Index: /trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.5.0/ALGLIB-3.5.0/ALGLIB License.txt
===================================================================
--- /trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.5.0/ALGLIB-3.5.0/ALGLIB License.txt	(revision 7673)
+++ /trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.5.0/ALGLIB-3.5.0/ALGLIB License.txt	(revision 7673)
@@ -0,0 +1,17 @@
+﻿ALGLIB® - numerical analysis library, 1999-2012.
+ALGLIB is registered trademark of the ALGLIB Project.
+
+
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation (www.fsf.org); either version 2 of the 
+License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+A copy of the GNU General Public License is available at
+http://www.fsf.org/licensing/licenses
Index: /trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.5.0/ALGLIB-3.5.0/ALGLIB-3.5.0.csproj
===================================================================
--- /trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.5.0/ALGLIB-3.5.0/ALGLIB-3.5.0.csproj	(revision 7673)
+++ /trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.5.0/ALGLIB-3.5.0/ALGLIB-3.5.0.csproj	(revision 7673)
@@ -0,0 +1,155 @@
+﻿<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProductVersion>9.0.30729</ProductVersion>
+    <SchemaVersion>2.0</SchemaVersion>
+    <ProjectGuid>{D863807E-4774-455E-85A4-96E7E6A8F68E}</ProjectGuid>
+    <OutputType>Library</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>ALGLIB</RootNamespace>
+    <AssemblyName>ALGLIB-3.5.0</AssemblyName>
+    <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
+    <TargetFrameworkProfile>
+    </TargetFrameworkProfile>
+    <FileAlignment>512</FileAlignment>
+    <SignAssembly>true</SignAssembly>
+    <AssemblyOriginatorKeyFile>HeuristicLab.snk</AssemblyOriginatorKeyFile>
+    <FileUpgradeFlags>
+    </FileUpgradeFlags>
+    <OldToolsVersion>3.5</OldToolsVersion>
+    <UpgradeBackupLocation />
+    <PublishUrl>publish\</PublishUrl>
+    <Install>true</Install>
+    <InstallFrom>Disk</InstallFrom>
+    <UpdateEnabled>false</UpdateEnabled>
+    <UpdateMode>Foreground</UpdateMode>
+    <UpdateInterval>7</UpdateInterval>
+    <UpdateIntervalUnits>Days</UpdateIntervalUnits>
+    <UpdatePeriodically>false</UpdatePeriodically>
+    <UpdateRequired>false</UpdateRequired>
+    <MapFileExtensions>true</MapFileExtensions>
+    <ApplicationRevision>0</ApplicationRevision>
+    <ApplicationVersion>1.0.0.%2a</ApplicationVersion>
+    <IsWebBootstrapper>false</IsWebBootstrapper>
+    <UseApplicationTrust>false</UseApplicationTrust>
+    <BootstrapperEnabled>true</BootstrapperEnabled>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>$(SolutionDir)\bin\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>$(SolutionDir)\bin\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
+    <DebugSymbols>true</DebugSymbols>
+    <OutputPath>$(SolutionDir)\bin\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <DebugType>full</DebugType>
+    <PlatformTarget>x86</PlatformTarget>
+    <ErrorReport>prompt</ErrorReport>
+    <CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
+    <OutputPath>$(SolutionDir)\bin\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <Optimize>true</Optimize>
+    <DebugType>pdbonly</DebugType>
+    <PlatformTarget>x86</PlatformTarget>
+    <ErrorReport>prompt</ErrorReport>
+    <CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x64' ">
+    <DebugSymbols>true</DebugSymbols>
+    <OutputPath>$(SolutionDir)\bin\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <DebugType>full</DebugType>
+    <PlatformTarget>x64</PlatformTarget>
+    <ErrorReport>prompt</ErrorReport>
+    <CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x64' ">
+    <OutputPath>$(SolutionDir)\bin\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <Optimize>true</Optimize>
+    <DebugType>pdbonly</DebugType>
+    <PlatformTarget>x64</PlatformTarget>
+    <ErrorReport>prompt</ErrorReport>
+    <CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="System" />
+    <Reference Include="System.Core">
+      <RequiredTargetFramework>3.5</RequiredTargetFramework>
+    </Reference>
+    <Reference Include="System.Xml.Linq">
+      <RequiredTargetFramework>3.5</RequiredTargetFramework>
+    </Reference>
+    <Reference Include="System.Data.DataSetExtensions">
+      <RequiredTargetFramework>3.5</RequiredTargetFramework>
+    </Reference>
+    <Reference Include="System.Data" />
+    <Reference Include="System.Xml" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="alglibinternal.cs" />
+    <Compile Include="alglibmisc.cs" />
+    <Compile Include="ap.cs" />
+    <Compile Include="dataanalysis.cs" />
+    <Compile Include="diffequations.cs" />
+    <Compile Include="fasttransforms.cs" />
+    <Compile Include="integration.cs" />
+    <Compile Include="interpolation.cs" />
+    <Compile Include="linalg.cs" />
+    <Compile Include="optimization.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="solvers.cs" />
+    <Compile Include="specialfunctions.cs" />
+    <Compile Include="statistics.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <Content Include="ALGLIB License.txt" />
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="HeuristicLab.snk" />
+  </ItemGroup>
+  <ItemGroup>
+    <BootstrapperPackage Include="Microsoft.Net.Client.3.5">
+      <Visible>False</Visible>
+      <ProductName>.NET Framework 3.5 SP1 Client Profile</ProductName>
+      <Install>false</Install>
+    </BootstrapperPackage>
+    <BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
+      <Visible>False</Visible>
+      <ProductName>.NET Framework 3.5 SP1</ProductName>
+      <Install>true</Install>
+    </BootstrapperPackage>
+    <BootstrapperPackage Include="Microsoft.Windows.Installer.3.1">
+      <Visible>False</Visible>
+      <ProductName>Windows Installer 3.1</ProductName>
+      <Install>true</Install>
+    </BootstrapperPackage>
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
+       Other similar extension points exist, see Microsoft.Common.targets.
+  <Target Name="BeforeBuild">
+  </Target>
+  <Target Name="AfterBuild">
+  </Target>
+  -->
+</Project>
Index: /trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.5.0/ALGLIB-3.5.0/Properties/AssemblyInfo.cs
===================================================================
--- /trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.5.0/ALGLIB-3.5.0/Properties/AssemblyInfo.cs	(revision 7673)
+++ /trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.5.0/ALGLIB-3.5.0/Properties/AssemblyInfo.cs	(revision 7673)
@@ -0,0 +1,35 @@
+﻿using System.Reflection;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following 
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("ALGLIB-3.5.0")]
+[assembly: AssemblyDescription("ALGLIB® - numerical analysis library, 1999-2012")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("ALGLIB project")]
+[assembly: AssemblyProduct("ALGLIB-3.5.0")]
+[assembly: AssemblyCopyright("Copyright © Sergey Bochkanov (ALGLIB project) 1999-2012")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible 
+// to COM components.  If you need to access a type in this assembly from 
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("4703AE78-3EA4-405A-921B-1FAF8DECA3F2")]
+
+// Version information for an assembly consists of the following four values:
+//
+//      Major Version
+//      Minor Version 
+//      Build Number
+//      Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers 
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("3.5.0.0")]
+[assembly: AssemblyFileVersion("3.5.0.0")]
Index: /trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.5.0/ALGLIB-3.5.0/alglibinternal.cs
===================================================================
--- /trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.5.0/ALGLIB-3.5.0/alglibinternal.cs	(revision 7673)
+++ /trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.5.0/ALGLIB-3.5.0/alglibinternal.cs	(revision 7673)
@@ -0,0 +1,11493 @@
+/*************************************************************************
+Copyright (c) Sergey Bochkanov (ALGLIB project).
+
+>>> SOURCE LICENSE >>>
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation (www.fsf.org); either version 2 of the
+License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+A copy of the GNU General Public License is available at
+http://www.fsf.org/licensing/licenses
+>>> END OF LICENSE >>>
+*************************************************************************/
+#pragma warning disable 162
+#pragma warning disable 219
+using System;
+
+public partial class alglib
+{
+
+
+
+}
+public partial class alglib
+{
+    public class scodes
+    {
+        public static int getrdfserializationcode()
+        {
+            int result = 0;
+
+            result = 1;
+            return result;
+        }
+
+
+        public static int getkdtreeserializationcode()
+        {
+            int result = 0;
+
+            result = 2;
+            return result;
+        }
+
+
+        public static int getmlpserializationcode()
+        {
+            int result = 0;
+
+            result = 3;
+            return result;
+        }
+
+
+        public static int getmlpeserializationcode()
+        {
+            int result = 0;
+
+            result = 4;
+            return result;
+        }
+
+
+        public static int getrbfserializationcode()
+        {
+            int result = 0;
+
+            result = 5;
+            return result;
+        }
+
+
+    }
+    public class apserv
+    {
+        /*************************************************************************
+        Buffers for internal functions which need buffers:
+        * check for size of the buffer you want to use.
+        * if buffer is too small, resize it; leave unchanged, if it is larger than
+          needed.
+        * use it.
+
+        We can pass this structure to multiple functions;  after first run through
+        functions buffer sizes will be finally determined,  and  on  a next run no
+        allocation will be required.
+        *************************************************************************/
+        public class apbuffers
+        {
+            public int[] ia0;
+            public int[] ia1;
+            public int[] ia2;
+            public int[] ia3;
+            public double[] ra0;
+            public double[] ra1;
+            public double[] ra2;
+            public double[] ra3;
+            public apbuffers()
+            {
+                ia0 = new int[0];
+                ia1 = new int[0];
+                ia2 = new int[0];
+                ia3 = new int[0];
+                ra0 = new double[0];
+                ra1 = new double[0];
+                ra2 = new double[0];
+                ra3 = new double[0];
+            }
+        };
+
+
+
+
+        /*************************************************************************
+        This function compares two numbers for approximate equality, with tolerance
+        to errors as large as max(|a|,|b|)*tol.
+
+
+          -- ALGLIB --
+             Copyright 02.12.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static bool approxequalrel(double a,
+            double b,
+            double tol)
+        {
+            bool result = new bool();
+
+            result = (double)(Math.Abs(a-b))<=(double)(Math.Max(Math.Abs(a), Math.Abs(b))*tol);
+            return result;
+        }
+
+
+        /*************************************************************************
+        This  function  generates  1-dimensional  general  interpolation task with
+        moderate Lipshitz constant (close to 1.0)
+
+        If N=1 then suborutine generates only one point at the middle of [A,B]
+
+          -- ALGLIB --
+             Copyright 02.12.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void taskgenint1d(double a,
+            double b,
+            int n,
+            ref double[] x,
+            ref double[] y)
+        {
+            int i = 0;
+            double h = 0;
+
+            x = new double[0];
+            y = new double[0];
+
+            alglib.ap.assert(n>=1, "TaskGenInterpolationEqdist1D: N<1!");
+            x = new double[n];
+            y = new double[n];
+            if( n>1 )
+            {
+                x[0] = a;
+                y[0] = 2*math.randomreal()-1;
+                h = (b-a)/(n-1);
+                for(i=1; i<=n-1; i++)
+                {
+                    if( i!=n-1 )
+                    {
+                        x[i] = a+(i+0.2*(2*math.randomreal()-1))*h;
+                    }
+                    else
+                    {
+                        x[i] = b;
+                    }
+                    y[i] = y[i-1]+(2*math.randomreal()-1)*(x[i]-x[i-1]);
+                }
+            }
+            else
+            {
+                x[0] = 0.5*(a+b);
+                y[0] = 2*math.randomreal()-1;
+            }
+        }
+
+
+        /*************************************************************************
+        This function generates  1-dimensional equidistant interpolation task with
+        moderate Lipshitz constant (close to 1.0)
+
+        If N=1 then suborutine generates only one point at the middle of [A,B]
+
+          -- ALGLIB --
+             Copyright 02.12.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void taskgenint1dequidist(double a,
+            double b,
+            int n,
+            ref double[] x,
+            ref double[] y)
+        {
+            int i = 0;
+            double h = 0;
+
+            x = new double[0];
+            y = new double[0];
+
+            alglib.ap.assert(n>=1, "TaskGenInterpolationEqdist1D: N<1!");
+            x = new double[n];
+            y = new double[n];
+            if( n>1 )
+            {
+                x[0] = a;
+                y[0] = 2*math.randomreal()-1;
+                h = (b-a)/(n-1);
+                for(i=1; i<=n-1; i++)
+                {
+                    x[i] = a+i*h;
+                    y[i] = y[i-1]+(2*math.randomreal()-1)*h;
+                }
+            }
+            else
+            {
+                x[0] = 0.5*(a+b);
+                y[0] = 2*math.randomreal()-1;
+            }
+        }
+
+
+        /*************************************************************************
+        This function generates  1-dimensional Chebyshev-1 interpolation task with
+        moderate Lipshitz constant (close to 1.0)
+
+        If N=1 then suborutine generates only one point at the middle of [A,B]
+
+          -- ALGLIB --
+             Copyright 02.12.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void taskgenint1dcheb1(double a,
+            double b,
+            int n,
+            ref double[] x,
+            ref double[] y)
+        {
+            int i = 0;
+
+            x = new double[0];
+            y = new double[0];
+
+            alglib.ap.assert(n>=1, "TaskGenInterpolation1DCheb1: N<1!");
+            x = new double[n];
+            y = new double[n];
+            if( n>1 )
+            {
+                for(i=0; i<=n-1; i++)
+                {
+                    x[i] = 0.5*(b+a)+0.5*(b-a)*Math.Cos(Math.PI*(2*i+1)/(2*n));
+                    if( i==0 )
+                    {
+                        y[i] = 2*math.randomreal()-1;
+                    }
+                    else
+                    {
+                        y[i] = y[i-1]+(2*math.randomreal()-1)*(x[i]-x[i-1]);
+                    }
+                }
+            }
+            else
+            {
+                x[0] = 0.5*(a+b);
+                y[0] = 2*math.randomreal()-1;
+            }
+        }
+
+
+        /*************************************************************************
+        This function generates  1-dimensional Chebyshev-2 interpolation task with
+        moderate Lipshitz constant (close to 1.0)
+
+        If N=1 then suborutine generates only one point at the middle of [A,B]
+
+          -- ALGLIB --
+             Copyright 02.12.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void taskgenint1dcheb2(double a,
+            double b,
+            int n,
+            ref double[] x,
+            ref double[] y)
+        {
+            int i = 0;
+
+            x = new double[0];
+            y = new double[0];
+
+            alglib.ap.assert(n>=1, "TaskGenInterpolation1DCheb2: N<1!");
+            x = new double[n];
+            y = new double[n];
+            if( n>1 )
+            {
+                for(i=0; i<=n-1; i++)
+                {
+                    x[i] = 0.5*(b+a)+0.5*(b-a)*Math.Cos(Math.PI*i/(n-1));
+                    if( i==0 )
+                    {
+                        y[i] = 2*math.randomreal()-1;
+                    }
+                    else
+                    {
+                        y[i] = y[i-1]+(2*math.randomreal()-1)*(x[i]-x[i-1]);
+                    }
+                }
+            }
+            else
+            {
+                x[0] = 0.5*(a+b);
+                y[0] = 2*math.randomreal()-1;
+            }
+        }
+
+
+        /*************************************************************************
+        This function checks that all values from X[] are distinct. It does more
+        than just usual floating point comparison:
+        * first, it calculates max(X) and min(X)
+        * second, it maps X[] from [min,max] to [1,2]
+        * only at this stage actual comparison is done
+
+        The meaning of such check is to ensure that all values are "distinct enough"
+        and will not cause interpolation subroutine to fail.
+
+        NOTE:
+            X[] must be sorted by ascending (subroutine ASSERT's it)
+
+          -- ALGLIB --
+             Copyright 02.12.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static bool aredistinct(double[] x,
+            int n)
+        {
+            bool result = new bool();
+            double a = 0;
+            double b = 0;
+            int i = 0;
+            bool nonsorted = new bool();
+
+            alglib.ap.assert(n>=1, "APSERVAreDistinct: internal error (N<1)");
+            if( n==1 )
+            {
+                
+                //
+                // everything is alright, it is up to caller to decide whether it
+                // can interpolate something with just one point
+                //
+                result = true;
+                return result;
+            }
+            a = x[0];
+            b = x[0];
+            nonsorted = false;
+            for(i=1; i<=n-1; i++)
+            {
+                a = Math.Min(a, x[i]);
+                b = Math.Max(b, x[i]);
+                nonsorted = nonsorted || (double)(x[i-1])>=(double)(x[i]);
+            }
+            alglib.ap.assert(!nonsorted, "APSERVAreDistinct: internal error (not sorted)");
+            for(i=1; i<=n-1; i++)
+            {
+                if( (double)((x[i]-a)/(b-a)+1)==(double)((x[i-1]-a)/(b-a)+1) )
+                {
+                    result = false;
+                    return result;
+                }
+            }
+            result = true;
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function checks that two boolean values are the same (both  are  True 
+        or both are False).
+
+          -- ALGLIB --
+             Copyright 02.12.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static bool aresameboolean(bool v1,
+            bool v2)
+        {
+            bool result = new bool();
+
+            result = (v1 && v2) || (!v1 && !v2);
+            return result;
+        }
+
+
+        /*************************************************************************
+        If Length(X)<N, resizes X
+
+          -- ALGLIB --
+             Copyright 20.03.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void bvectorsetlengthatleast(ref bool[] x,
+            int n)
+        {
+            if( alglib.ap.len(x)<n )
+            {
+                x = new bool[n];
+            }
+        }
+
+
+        /*************************************************************************
+        If Length(X)<N, resizes X
+
+          -- ALGLIB --
+             Copyright 20.03.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void ivectorsetlengthatleast(ref int[] x,
+            int n)
+        {
+            if( alglib.ap.len(x)<n )
+            {
+                x = new int[n];
+            }
+        }
+
+
+        /*************************************************************************
+        If Length(X)<N, resizes X
+
+          -- ALGLIB --
+             Copyright 20.03.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rvectorsetlengthatleast(ref double[] x,
+            int n)
+        {
+            if( alglib.ap.len(x)<n )
+            {
+                x = new double[n];
+            }
+        }
+
+
+        /*************************************************************************
+        If Cols(X)<N or Rows(X)<M, resizes X
+
+          -- ALGLIB --
+             Copyright 20.03.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rmatrixsetlengthatleast(ref double[,] x,
+            int m,
+            int n)
+        {
+            if( m>0 && n>0 )
+            {
+                if( alglib.ap.rows(x)<m || alglib.ap.cols(x)<n )
+                {
+                    x = new double[m, n];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Resizes X and:
+        * preserves old contents of X
+        * fills new elements by zeros
+
+          -- ALGLIB --
+             Copyright 20.03.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rmatrixresize(ref double[,] x,
+            int m,
+            int n)
+        {
+            double[,] oldx = new double[0,0];
+            int i = 0;
+            int j = 0;
+            int m2 = 0;
+            int n2 = 0;
+
+            m2 = alglib.ap.rows(x);
+            n2 = alglib.ap.cols(x);
+            alglib.ap.swap(ref x, ref oldx);
+            x = new double[m, n];
+            for(i=0; i<=m-1; i++)
+            {
+                for(j=0; j<=n-1; j++)
+                {
+                    if( i<m2 && j<n2 )
+                    {
+                        x[i,j] = oldx[i,j];
+                    }
+                    else
+                    {
+                        x[i,j] = 0.0;
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This function checks that all values from X[] are finite
+
+          -- ALGLIB --
+             Copyright 18.06.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static bool isfinitevector(double[] x,
+            int n)
+        {
+            bool result = new bool();
+            int i = 0;
+
+            alglib.ap.assert(n>=0, "APSERVIsFiniteVector: internal error (N<0)");
+            for(i=0; i<=n-1; i++)
+            {
+                if( !math.isfinite(x[i]) )
+                {
+                    result = false;
+                    return result;
+                }
+            }
+            result = true;
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function checks that all values from X[] are finite
+
+          -- ALGLIB --
+             Copyright 18.06.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static bool isfinitecvector(complex[] z,
+            int n)
+        {
+            bool result = new bool();
+            int i = 0;
+
+            alglib.ap.assert(n>=0, "APSERVIsFiniteCVector: internal error (N<0)");
+            for(i=0; i<=n-1; i++)
+            {
+                if( !math.isfinite(z[i].x) || !math.isfinite(z[i].y) )
+                {
+                    result = false;
+                    return result;
+                }
+            }
+            result = true;
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function checks that all values from X[0..M-1,0..N-1] are finite
+
+          -- ALGLIB --
+             Copyright 18.06.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static bool apservisfinitematrix(double[,] x,
+            int m,
+            int n)
+        {
+            bool result = new bool();
+            int i = 0;
+            int j = 0;
+
+            alglib.ap.assert(n>=0, "APSERVIsFiniteMatrix: internal error (N<0)");
+            alglib.ap.assert(m>=0, "APSERVIsFiniteMatrix: internal error (M<0)");
+            for(i=0; i<=m-1; i++)
+            {
+                for(j=0; j<=n-1; j++)
+                {
+                    if( !math.isfinite(x[i,j]) )
+                    {
+                        result = false;
+                        return result;
+                    }
+                }
+            }
+            result = true;
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function checks that all values from X[0..M-1,0..N-1] are finite
+
+          -- ALGLIB --
+             Copyright 18.06.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static bool apservisfinitecmatrix(complex[,] x,
+            int m,
+            int n)
+        {
+            bool result = new bool();
+            int i = 0;
+            int j = 0;
+
+            alglib.ap.assert(n>=0, "APSERVIsFiniteCMatrix: internal error (N<0)");
+            alglib.ap.assert(m>=0, "APSERVIsFiniteCMatrix: internal error (M<0)");
+            for(i=0; i<=m-1; i++)
+            {
+                for(j=0; j<=n-1; j++)
+                {
+                    if( !math.isfinite(x[i,j].x) || !math.isfinite(x[i,j].y) )
+                    {
+                        result = false;
+                        return result;
+                    }
+                }
+            }
+            result = true;
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function checks that all values from upper/lower triangle of
+        X[0..N-1,0..N-1] are finite
+
+          -- ALGLIB --
+             Copyright 18.06.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static bool isfinitertrmatrix(double[,] x,
+            int n,
+            bool isupper)
+        {
+            bool result = new bool();
+            int i = 0;
+            int j1 = 0;
+            int j2 = 0;
+            int j = 0;
+
+            alglib.ap.assert(n>=0, "APSERVIsFiniteRTRMatrix: internal error (N<0)");
+            for(i=0; i<=n-1; i++)
+            {
+                if( isupper )
+                {
+                    j1 = i;
+                    j2 = n-1;
+                }
+                else
+                {
+                    j1 = 0;
+                    j2 = i;
+                }
+                for(j=j1; j<=j2; j++)
+                {
+                    if( !math.isfinite(x[i,j]) )
+                    {
+                        result = false;
+                        return result;
+                    }
+                }
+            }
+            result = true;
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function checks that all values from upper/lower triangle of
+        X[0..N-1,0..N-1] are finite
+
+          -- ALGLIB --
+             Copyright 18.06.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static bool apservisfinitectrmatrix(complex[,] x,
+            int n,
+            bool isupper)
+        {
+            bool result = new bool();
+            int i = 0;
+            int j1 = 0;
+            int j2 = 0;
+            int j = 0;
+
+            alglib.ap.assert(n>=0, "APSERVIsFiniteCTRMatrix: internal error (N<0)");
+            for(i=0; i<=n-1; i++)
+            {
+                if( isupper )
+                {
+                    j1 = i;
+                    j2 = n-1;
+                }
+                else
+                {
+                    j1 = 0;
+                    j2 = i;
+                }
+                for(j=j1; j<=j2; j++)
+                {
+                    if( !math.isfinite(x[i,j].x) || !math.isfinite(x[i,j].y) )
+                    {
+                        result = false;
+                        return result;
+                    }
+                }
+            }
+            result = true;
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function checks that all values from X[0..M-1,0..N-1] are  finite  or
+        NaN's.
+
+          -- ALGLIB --
+             Copyright 18.06.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static bool apservisfiniteornanmatrix(double[,] x,
+            int m,
+            int n)
+        {
+            bool result = new bool();
+            int i = 0;
+            int j = 0;
+
+            alglib.ap.assert(n>=0, "APSERVIsFiniteOrNaNMatrix: internal error (N<0)");
+            alglib.ap.assert(m>=0, "APSERVIsFiniteOrNaNMatrix: internal error (M<0)");
+            for(i=0; i<=m-1; i++)
+            {
+                for(j=0; j<=n-1; j++)
+                {
+                    if( !(math.isfinite(x[i,j]) || Double.IsNaN(x[i,j])) )
+                    {
+                        result = false;
+                        return result;
+                    }
+                }
+            }
+            result = true;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Safe sqrt(x^2+y^2)
+
+          -- ALGLIB --
+             Copyright by Bochkanov Sergey
+        *************************************************************************/
+        public static double safepythag2(double x,
+            double y)
+        {
+            double result = 0;
+            double w = 0;
+            double xabs = 0;
+            double yabs = 0;
+            double z = 0;
+
+            xabs = Math.Abs(x);
+            yabs = Math.Abs(y);
+            w = Math.Max(xabs, yabs);
+            z = Math.Min(xabs, yabs);
+            if( (double)(z)==(double)(0) )
+            {
+                result = w;
+            }
+            else
+            {
+                result = w*Math.Sqrt(1+math.sqr(z/w));
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        Safe sqrt(x^2+y^2)
+
+          -- ALGLIB --
+             Copyright by Bochkanov Sergey
+        *************************************************************************/
+        public static double safepythag3(double x,
+            double y,
+            double z)
+        {
+            double result = 0;
+            double w = 0;
+
+            w = Math.Max(Math.Abs(x), Math.Max(Math.Abs(y), Math.Abs(z)));
+            if( (double)(w)==(double)(0) )
+            {
+                result = 0;
+                return result;
+            }
+            x = x/w;
+            y = y/w;
+            z = z/w;
+            result = w*Math.Sqrt(math.sqr(x)+math.sqr(y)+math.sqr(z));
+            return result;
+        }
+
+
+        /*************************************************************************
+        Safe division.
+
+        This function attempts to calculate R=X/Y without overflow.
+
+        It returns:
+        * +1, if abs(X/Y)>=MaxRealNumber or undefined - overflow-like situation
+              (no overlfow is generated, R is either NAN, PosINF, NegINF)
+        *  0, if MinRealNumber<abs(X/Y)<MaxRealNumber or X=0, Y<>0
+              (R contains result, may be zero)
+        * -1, if 0<abs(X/Y)<MinRealNumber - underflow-like situation
+              (R contains zero; it corresponds to underflow)
+
+        No overflow is generated in any case.
+
+          -- ALGLIB --
+             Copyright by Bochkanov Sergey
+        *************************************************************************/
+        public static int saferdiv(double x,
+            double y,
+            ref double r)
+        {
+            int result = 0;
+
+            r = 0;
+
+            
+            //
+            // Two special cases:
+            // * Y=0
+            // * X=0 and Y<>0
+            //
+            if( (double)(y)==(double)(0) )
+            {
+                result = 1;
+                if( (double)(x)==(double)(0) )
+                {
+                    r = Double.NaN;
+                }
+                if( (double)(x)>(double)(0) )
+                {
+                    r = Double.PositiveInfinity;
+                }
+                if( (double)(x)<(double)(0) )
+                {
+                    r = Double.NegativeInfinity;
+                }
+                return result;
+            }
+            if( (double)(x)==(double)(0) )
+            {
+                r = 0;
+                result = 0;
+                return result;
+            }
+            
+            //
+            // make Y>0
+            //
+            if( (double)(y)<(double)(0) )
+            {
+                x = -x;
+                y = -y;
+            }
+            
+            //
+            //
+            //
+            if( (double)(y)>=(double)(1) )
+            {
+                r = x/y;
+                if( (double)(Math.Abs(r))<=(double)(math.minrealnumber) )
+                {
+                    result = -1;
+                    r = 0;
+                }
+                else
+                {
+                    result = 0;
+                }
+            }
+            else
+            {
+                if( (double)(Math.Abs(x))>=(double)(math.maxrealnumber*y) )
+                {
+                    if( (double)(x)>(double)(0) )
+                    {
+                        r = Double.PositiveInfinity;
+                    }
+                    else
+                    {
+                        r = Double.NegativeInfinity;
+                    }
+                    result = 1;
+                }
+                else
+                {
+                    r = x/y;
+                    result = 0;
+                }
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function calculates "safe" min(X/Y,V) for positive finite X, Y, V.
+        No overflow is generated in any case.
+
+          -- ALGLIB --
+             Copyright by Bochkanov Sergey
+        *************************************************************************/
+        public static double safeminposrv(double x,
+            double y,
+            double v)
+        {
+            double result = 0;
+            double r = 0;
+
+            if( (double)(y)>=(double)(1) )
+            {
+                
+                //
+                // Y>=1, we can safely divide by Y
+                //
+                r = x/y;
+                result = v;
+                if( (double)(v)>(double)(r) )
+                {
+                    result = r;
+                }
+                else
+                {
+                    result = v;
+                }
+            }
+            else
+            {
+                
+                //
+                // Y<1, we can safely multiply by Y
+                //
+                if( (double)(x)<(double)(v*y) )
+                {
+                    result = x/y;
+                }
+                else
+                {
+                    result = v;
+                }
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function makes periodic mapping of X to [A,B].
+
+        It accepts X, A, B (A>B). It returns T which lies in  [A,B] and integer K,
+        such that X = T + K*(B-A).
+
+        NOTES:
+        * K is represented as real value, although actually it is integer
+        * T is guaranteed to be in [A,B]
+        * T replaces X
+
+          -- ALGLIB --
+             Copyright by Bochkanov Sergey
+        *************************************************************************/
+        public static void apperiodicmap(ref double x,
+            double a,
+            double b,
+            ref double k)
+        {
+            k = 0;
+
+            alglib.ap.assert((double)(a)<(double)(b), "APPeriodicMap: internal error!");
+            k = (int)Math.Floor((x-a)/(b-a));
+            x = x-k*(b-a);
+            while( (double)(x)<(double)(a) )
+            {
+                x = x+(b-a);
+                k = k-1;
+            }
+            while( (double)(x)>(double)(b) )
+            {
+                x = x-(b-a);
+                k = k+1;
+            }
+            x = Math.Max(x, a);
+            x = Math.Min(x, b);
+        }
+
+
+        /*************************************************************************
+        'bounds' value: maps X to [B1,B2]
+
+          -- ALGLIB --
+             Copyright 20.03.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static double boundval(double x,
+            double b1,
+            double b2)
+        {
+            double result = 0;
+
+            if( (double)(x)<=(double)(b1) )
+            {
+                result = b1;
+                return result;
+            }
+            if( (double)(x)>=(double)(b2) )
+            {
+                result = b2;
+                return result;
+            }
+            result = x;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Allocation of serializer: complex value
+        *************************************************************************/
+        public static void alloccomplex(alglib.serializer s,
+            complex v)
+        {
+            s.alloc_entry();
+            s.alloc_entry();
+        }
+
+
+        /*************************************************************************
+        Serialization: complex value
+        *************************************************************************/
+        public static void serializecomplex(alglib.serializer s,
+            complex v)
+        {
+            s.serialize_double(v.x);
+            s.serialize_double(v.y);
+        }
+
+
+        /*************************************************************************
+        Unserialization: complex value
+        *************************************************************************/
+        public static complex unserializecomplex(alglib.serializer s)
+        {
+            complex result = 0;
+
+            result.x = s.unserialize_double();
+            result.y = s.unserialize_double();
+            return result;
+        }
+
+
+        /*************************************************************************
+        Allocation of serializer: real array
+        *************************************************************************/
+        public static void allocrealarray(alglib.serializer s,
+            double[] v,
+            int n)
+        {
+            int i = 0;
+
+            if( n<0 )
+            {
+                n = alglib.ap.len(v);
+            }
+            s.alloc_entry();
+            for(i=0; i<=n-1; i++)
+            {
+                s.alloc_entry();
+            }
+        }
+
+
+        /*************************************************************************
+        Serialization: complex value
+        *************************************************************************/
+        public static void serializerealarray(alglib.serializer s,
+            double[] v,
+            int n)
+        {
+            int i = 0;
+
+            if( n<0 )
+            {
+                n = alglib.ap.len(v);
+            }
+            s.serialize_int(n);
+            for(i=0; i<=n-1; i++)
+            {
+                s.serialize_double(v[i]);
+            }
+        }
+
+
+        /*************************************************************************
+        Unserialization: complex value
+        *************************************************************************/
+        public static void unserializerealarray(alglib.serializer s,
+            ref double[] v)
+        {
+            int n = 0;
+            int i = 0;
+            double t = 0;
+
+            v = new double[0];
+
+            n = s.unserialize_int();
+            if( n==0 )
+            {
+                return;
+            }
+            v = new double[n];
+            for(i=0; i<=n-1; i++)
+            {
+                t = s.unserialize_double();
+                v[i] = t;
+            }
+        }
+
+
+        /*************************************************************************
+        Allocation of serializer: Integer array
+        *************************************************************************/
+        public static void allocintegerarray(alglib.serializer s,
+            int[] v,
+            int n)
+        {
+            int i = 0;
+
+            if( n<0 )
+            {
+                n = alglib.ap.len(v);
+            }
+            s.alloc_entry();
+            for(i=0; i<=n-1; i++)
+            {
+                s.alloc_entry();
+            }
+        }
+
+
+        /*************************************************************************
+        Serialization: Integer array
+        *************************************************************************/
+        public static void serializeintegerarray(alglib.serializer s,
+            int[] v,
+            int n)
+        {
+            int i = 0;
+
+            if( n<0 )
+            {
+                n = alglib.ap.len(v);
+            }
+            s.serialize_int(n);
+            for(i=0; i<=n-1; i++)
+            {
+                s.serialize_int(v[i]);
+            }
+        }
+
+
+        /*************************************************************************
+        Unserialization: complex value
+        *************************************************************************/
+        public static void unserializeintegerarray(alglib.serializer s,
+            ref int[] v)
+        {
+            int n = 0;
+            int i = 0;
+            int t = 0;
+
+            v = new int[0];
+
+            n = s.unserialize_int();
+            if( n==0 )
+            {
+                return;
+            }
+            v = new int[n];
+            for(i=0; i<=n-1; i++)
+            {
+                t = s.unserialize_int();
+                v[i] = t;
+            }
+        }
+
+
+        /*************************************************************************
+        Allocation of serializer: real matrix
+        *************************************************************************/
+        public static void allocrealmatrix(alglib.serializer s,
+            double[,] v,
+            int n0,
+            int n1)
+        {
+            int i = 0;
+            int j = 0;
+
+            if( n0<0 )
+            {
+                n0 = alglib.ap.rows(v);
+            }
+            if( n1<0 )
+            {
+                n1 = alglib.ap.cols(v);
+            }
+            s.alloc_entry();
+            s.alloc_entry();
+            for(i=0; i<=n0-1; i++)
+            {
+                for(j=0; j<=n1-1; j++)
+                {
+                    s.alloc_entry();
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Serialization: complex value
+        *************************************************************************/
+        public static void serializerealmatrix(alglib.serializer s,
+            double[,] v,
+            int n0,
+            int n1)
+        {
+            int i = 0;
+            int j = 0;
+
+            if( n0<0 )
+            {
+                n0 = alglib.ap.rows(v);
+            }
+            if( n1<0 )
+            {
+                n1 = alglib.ap.cols(v);
+            }
+            s.serialize_int(n0);
+            s.serialize_int(n1);
+            for(i=0; i<=n0-1; i++)
+            {
+                for(j=0; j<=n1-1; j++)
+                {
+                    s.serialize_double(v[i,j]);
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Unserialization: complex value
+        *************************************************************************/
+        public static void unserializerealmatrix(alglib.serializer s,
+            ref double[,] v)
+        {
+            int i = 0;
+            int j = 0;
+            int n0 = 0;
+            int n1 = 0;
+            double t = 0;
+
+            v = new double[0,0];
+
+            n0 = s.unserialize_int();
+            n1 = s.unserialize_int();
+            if( n0==0 || n1==0 )
+            {
+                return;
+            }
+            v = new double[n0, n1];
+            for(i=0; i<=n0-1; i++)
+            {
+                for(j=0; j<=n1-1; j++)
+                {
+                    t = s.unserialize_double();
+                    v[i,j] = t;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Copy integer array
+        *************************************************************************/
+        public static void copyintegerarray(int[] src,
+            ref int[] dst)
+        {
+            int i = 0;
+
+            dst = new int[0];
+
+            if( alglib.ap.len(src)>0 )
+            {
+                dst = new int[alglib.ap.len(src)];
+                for(i=0; i<=alglib.ap.len(src)-1; i++)
+                {
+                    dst[i] = src[i];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Copy real array
+        *************************************************************************/
+        public static void copyrealarray(double[] src,
+            ref double[] dst)
+        {
+            int i = 0;
+
+            dst = new double[0];
+
+            if( alglib.ap.len(src)>0 )
+            {
+                dst = new double[alglib.ap.len(src)];
+                for(i=0; i<=alglib.ap.len(src)-1; i++)
+                {
+                    dst[i] = src[i];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Copy real matrix
+        *************************************************************************/
+        public static void copyrealmatrix(double[,] src,
+            ref double[,] dst)
+        {
+            int i = 0;
+            int j = 0;
+
+            dst = new double[0,0];
+
+            if( alglib.ap.rows(src)>0 && alglib.ap.cols(src)>0 )
+            {
+                dst = new double[alglib.ap.rows(src), alglib.ap.cols(src)];
+                for(i=0; i<=alglib.ap.rows(src)-1; i++)
+                {
+                    for(j=0; j<=alglib.ap.cols(src)-1; j++)
+                    {
+                        dst[i,j] = src[i,j];
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This function searches integer array. Elements in this array are actually
+        records, each NRec elements wide. Each record has unique header - NHeader
+        integer values, which identify it. Records are lexicographically sorted by
+        header.
+
+        Records are identified by their index, not offset (offset = NRec*index).
+
+        This function searches A (records with indices [I0,I1)) for a record with
+        header B. It returns index of this record (not offset!), or -1 on failure.
+
+          -- ALGLIB --
+             Copyright 28.03.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static int recsearch(ref int[] a,
+            int nrec,
+            int nheader,
+            int i0,
+            int i1,
+            int[] b)
+        {
+            int result = 0;
+            int mididx = 0;
+            int cflag = 0;
+            int k = 0;
+            int offs = 0;
+
+            result = -1;
+            while( true )
+            {
+                if( i0>=i1 )
+                {
+                    break;
+                }
+                mididx = (i0+i1)/2;
+                offs = nrec*mididx;
+                cflag = 0;
+                for(k=0; k<=nheader-1; k++)
+                {
+                    if( a[offs+k]<b[k] )
+                    {
+                        cflag = -1;
+                        break;
+                    }
+                    if( a[offs+k]>b[k] )
+                    {
+                        cflag = 1;
+                        break;
+                    }
+                }
+                if( cflag==0 )
+                {
+                    result = mididx;
+                    return result;
+                }
+                if( cflag<0 )
+                {
+                    i0 = mididx+1;
+                }
+                else
+                {
+                    i1 = mididx;
+                }
+            }
+            return result;
+        }
+
+
+    }
+    public class tsort
+    {
+        /*************************************************************************
+        This function sorts array of real keys by ascending.
+
+        Its results are:
+        * sorted array A
+        * permutation tables P1, P2
+
+        Algorithm outputs permutation tables using two formats:
+        * as usual permutation of [0..N-1]. If P1[i]=j, then sorted A[i]  contains
+          value which was moved there from J-th position.
+        * as a sequence of pairwise permutations. Sorted A[] may  be  obtained  by
+          swaping A[i] and A[P2[i]] for all i from 0 to N-1.
+          
+        INPUT PARAMETERS:
+            A       -   unsorted array
+            N       -   array size
+
+        OUPUT PARAMETERS:
+            A       -   sorted array
+            P1, P2  -   permutation tables, array[N]
+            
+        NOTES:
+            this function assumes that A[] is finite; it doesn't checks that
+            condition. All other conditions (size of input arrays, etc.) are not
+            checked too.
+
+          -- ALGLIB --
+             Copyright 14.05.2008 by Bochkanov Sergey
+        *************************************************************************/
+        public static void tagsort(ref double[] a,
+            int n,
+            ref int[] p1,
+            ref int[] p2)
+        {
+            apserv.apbuffers buf = new apserv.apbuffers();
+
+            p1 = new int[0];
+            p2 = new int[0];
+
+            tagsortbuf(ref a, n, ref p1, ref p2, buf);
+        }
+
+
+        /*************************************************************************
+        Buffered variant of TagSort, which accepts preallocated output arrays as
+        well as special structure for buffered allocations. If arrays are too
+        short, they are reallocated. If they are large enough, no memory
+        allocation is done.
+
+        It is intended to be used in the performance-critical parts of code, where
+        additional allocations can lead to severe performance degradation
+
+          -- ALGLIB --
+             Copyright 14.05.2008 by Bochkanov Sergey
+        *************************************************************************/
+        public static void tagsortbuf(ref double[] a,
+            int n,
+            ref int[] p1,
+            ref int[] p2,
+            apserv.apbuffers buf)
+        {
+            int i = 0;
+            int lv = 0;
+            int lp = 0;
+            int rv = 0;
+            int rp = 0;
+
+            
+            //
+            // Special cases
+            //
+            if( n<=0 )
+            {
+                return;
+            }
+            if( n==1 )
+            {
+                apserv.ivectorsetlengthatleast(ref p1, 1);
+                apserv.ivectorsetlengthatleast(ref p2, 1);
+                p1[0] = 0;
+                p2[0] = 0;
+                return;
+            }
+            
+            //
+            // General case, N>1: prepare permutations table P1
+            //
+            apserv.ivectorsetlengthatleast(ref p1, n);
+            for(i=0; i<=n-1; i++)
+            {
+                p1[i] = i;
+            }
+            
+            //
+            // General case, N>1: sort, update P1
+            //
+            apserv.rvectorsetlengthatleast(ref buf.ra0, n);
+            apserv.ivectorsetlengthatleast(ref buf.ia0, n);
+            tagsortfasti(ref a, ref p1, ref buf.ra0, ref buf.ia0, n);
+            
+            //
+            // General case, N>1: fill permutations table P2
+            //
+            // To fill P2 we maintain two arrays:
+            // * PV (Buf.IA0), Position(Value). PV[i] contains position of I-th key at the moment
+            // * VP (Buf.IA1), Value(Position). VP[i] contains key which has position I at the moment
+            //
+            // At each step we making permutation of two items:
+            //   Left, which is given by position/value pair LP/LV
+            //   and Right, which is given by RP/RV
+            // and updating PV[] and VP[] correspondingly.
+            //
+            apserv.ivectorsetlengthatleast(ref buf.ia0, n);
+            apserv.ivectorsetlengthatleast(ref buf.ia1, n);
+            apserv.ivectorsetlengthatleast(ref p2, n);
+            for(i=0; i<=n-1; i++)
+            {
+                buf.ia0[i] = i;
+                buf.ia1[i] = i;
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                
+                //
+                // calculate LP, LV, RP, RV
+                //
+                lp = i;
+                lv = buf.ia1[lp];
+                rv = p1[i];
+                rp = buf.ia0[rv];
+                
+                //
+                // Fill P2
+                //
+                p2[i] = rp;
+                
+                //
+                // update PV and VP
+                //
+                buf.ia1[lp] = rv;
+                buf.ia1[rp] = lv;
+                buf.ia0[lv] = rp;
+                buf.ia0[rv] = lp;
+            }
+        }
+
+
+        /*************************************************************************
+        Same as TagSort, but optimized for real keys and integer labels.
+
+        A is sorted, and same permutations are applied to B.
+
+        NOTES:
+        1.  this function assumes that A[] is finite; it doesn't checks that
+            condition. All other conditions (size of input arrays, etc.) are not
+            checked too.
+        2.  this function uses two buffers, BufA and BufB, each is N elements large.
+            They may be preallocated (which will save some time) or not, in which
+            case function will automatically allocate memory.
+
+          -- ALGLIB --
+             Copyright 11.12.2008 by Bochkanov Sergey
+        *************************************************************************/
+        public static void tagsortfasti(ref double[] a,
+            ref int[] b,
+            ref double[] bufa,
+            ref int[] bufb,
+            int n)
+        {
+            int i = 0;
+            int j = 0;
+            bool isascending = new bool();
+            bool isdescending = new bool();
+            double tmpr = 0;
+            int tmpi = 0;
+
+            
+            //
+            // Special case
+            //
+            if( n<=1 )
+            {
+                return;
+            }
+            
+            //
+            // Test for already sorted set
+            //
+            isascending = true;
+            isdescending = true;
+            for(i=1; i<=n-1; i++)
+            {
+                isascending = isascending && a[i]>=a[i-1];
+                isdescending = isdescending && a[i]<=a[i-1];
+            }
+            if( isascending )
+            {
+                return;
+            }
+            if( isdescending )
+            {
+                for(i=0; i<=n-1; i++)
+                {
+                    j = n-1-i;
+                    if( j<=i )
+                    {
+                        break;
+                    }
+                    tmpr = a[i];
+                    a[i] = a[j];
+                    a[j] = tmpr;
+                    tmpi = b[i];
+                    b[i] = b[j];
+                    b[j] = tmpi;
+                }
+                return;
+            }
+            
+            //
+            // General case
+            //
+            if( alglib.ap.len(bufa)<n )
+            {
+                bufa = new double[n];
+            }
+            if( alglib.ap.len(bufb)<n )
+            {
+                bufb = new int[n];
+            }
+            tagsortfastirec(ref a, ref b, ref bufa, ref bufb, 0, n-1);
+        }
+
+
+        /*************************************************************************
+        Same as TagSort, but optimized for real keys and real labels.
+
+        A is sorted, and same permutations are applied to B.
+
+        NOTES:
+        1.  this function assumes that A[] is finite; it doesn't checks that
+            condition. All other conditions (size of input arrays, etc.) are not
+            checked too.
+        2.  this function uses two buffers, BufA and BufB, each is N elements large.
+            They may be preallocated (which will save some time) or not, in which
+            case function will automatically allocate memory.
+
+          -- ALGLIB --
+             Copyright 11.12.2008 by Bochkanov Sergey
+        *************************************************************************/
+        public static void tagsortfastr(ref double[] a,
+            ref double[] b,
+            ref double[] bufa,
+            ref double[] bufb,
+            int n)
+        {
+            int i = 0;
+            int j = 0;
+            bool isascending = new bool();
+            bool isdescending = new bool();
+            double tmpr = 0;
+
+            
+            //
+            // Special case
+            //
+            if( n<=1 )
+            {
+                return;
+            }
+            
+            //
+            // Test for already sorted set
+            //
+            isascending = true;
+            isdescending = true;
+            for(i=1; i<=n-1; i++)
+            {
+                isascending = isascending && a[i]>=a[i-1];
+                isdescending = isdescending && a[i]<=a[i-1];
+            }
+            if( isascending )
+            {
+                return;
+            }
+            if( isdescending )
+            {
+                for(i=0; i<=n-1; i++)
+                {
+                    j = n-1-i;
+                    if( j<=i )
+                    {
+                        break;
+                    }
+                    tmpr = a[i];
+                    a[i] = a[j];
+                    a[j] = tmpr;
+                    tmpr = b[i];
+                    b[i] = b[j];
+                    b[j] = tmpr;
+                }
+                return;
+            }
+            
+            //
+            // General case
+            //
+            if( alglib.ap.len(bufa)<n )
+            {
+                bufa = new double[n];
+            }
+            if( alglib.ap.len(bufb)<n )
+            {
+                bufb = new double[n];
+            }
+            tagsortfastrrec(ref a, ref b, ref bufa, ref bufb, 0, n-1);
+        }
+
+
+        /*************************************************************************
+        Same as TagSort, but optimized for real keys without labels.
+
+        A is sorted, and that's all.
+
+        NOTES:
+        1.  this function assumes that A[] is finite; it doesn't checks that
+            condition. All other conditions (size of input arrays, etc.) are not
+            checked too.
+        2.  this function uses buffer, BufA, which is N elements large. It may be
+            preallocated (which will save some time) or not, in which case
+            function will automatically allocate memory.
+
+          -- ALGLIB --
+             Copyright 11.12.2008 by Bochkanov Sergey
+        *************************************************************************/
+        public static void tagsortfast(ref double[] a,
+            ref double[] bufa,
+            int n)
+        {
+            int i = 0;
+            int j = 0;
+            bool isascending = new bool();
+            bool isdescending = new bool();
+            double tmpr = 0;
+
+            
+            //
+            // Special case
+            //
+            if( n<=1 )
+            {
+                return;
+            }
+            
+            //
+            // Test for already sorted set
+            //
+            isascending = true;
+            isdescending = true;
+            for(i=1; i<=n-1; i++)
+            {
+                isascending = isascending && a[i]>=a[i-1];
+                isdescending = isdescending && a[i]<=a[i-1];
+            }
+            if( isascending )
+            {
+                return;
+            }
+            if( isdescending )
+            {
+                for(i=0; i<=n-1; i++)
+                {
+                    j = n-1-i;
+                    if( j<=i )
+                    {
+                        break;
+                    }
+                    tmpr = a[i];
+                    a[i] = a[j];
+                    a[j] = tmpr;
+                }
+                return;
+            }
+            
+            //
+            // General case
+            //
+            if( alglib.ap.len(bufa)<n )
+            {
+                bufa = new double[n];
+            }
+            tagsortfastrec(ref a, ref bufa, 0, n-1);
+        }
+
+
+        /*************************************************************************
+        Sorting function optimized for integer keys and real labels, can be used
+        to sort middle of the array
+
+        A is sorted, and same permutations are applied to B.
+
+        NOTES:
+            this function assumes that A[] is finite; it doesn't checks that
+            condition. All other conditions (size of input arrays, etc.) are not
+            checked too.
+
+          -- ALGLIB --
+             Copyright 11.12.2008 by Bochkanov Sergey
+        *************************************************************************/
+        public static void tagsortmiddleir(ref int[] a,
+            ref double[] b,
+            int offset,
+            int n)
+        {
+            int i = 0;
+            int k = 0;
+            int t = 0;
+            int tmp = 0;
+            double tmpr = 0;
+
+            
+            //
+            // Special cases
+            //
+            if( n<=1 )
+            {
+                return;
+            }
+            
+            //
+            // General case, N>1: sort, update B
+            //
+            i = 2;
+            do
+            {
+                t = i;
+                while( t!=1 )
+                {
+                    k = t/2;
+                    if( a[offset+k-1]>=a[offset+t-1] )
+                    {
+                        t = 1;
+                    }
+                    else
+                    {
+                        tmp = a[offset+k-1];
+                        a[offset+k-1] = a[offset+t-1];
+                        a[offset+t-1] = tmp;
+                        tmpr = b[offset+k-1];
+                        b[offset+k-1] = b[offset+t-1];
+                        b[offset+t-1] = tmpr;
+                        t = k;
+                    }
+                }
+                i = i+1;
+            }
+            while( i<=n );
+            i = n-1;
+            do
+            {
+                tmp = a[offset+i];
+                a[offset+i] = a[offset+0];
+                a[offset+0] = tmp;
+                tmpr = b[offset+i];
+                b[offset+i] = b[offset+0];
+                b[offset+0] = tmpr;
+                t = 1;
+                while( t!=0 )
+                {
+                    k = 2*t;
+                    if( k>i )
+                    {
+                        t = 0;
+                    }
+                    else
+                    {
+                        if( k<i )
+                        {
+                            if( a[offset+k]>a[offset+k-1] )
+                            {
+                                k = k+1;
+                            }
+                        }
+                        if( a[offset+t-1]>=a[offset+k-1] )
+                        {
+                            t = 0;
+                        }
+                        else
+                        {
+                            tmp = a[offset+k-1];
+                            a[offset+k-1] = a[offset+t-1];
+                            a[offset+t-1] = tmp;
+                            tmpr = b[offset+k-1];
+                            b[offset+k-1] = b[offset+t-1];
+                            b[offset+t-1] = tmpr;
+                            t = k;
+                        }
+                    }
+                }
+                i = i-1;
+            }
+            while( i>=1 );
+        }
+
+
+        /*************************************************************************
+        Heap operations: adds element to the heap
+
+        PARAMETERS:
+            A       -   heap itself, must be at least array[0..N]
+            B       -   array of integer tags, which are updated according to
+                        permutations in the heap
+            N       -   size of the heap (without new element).
+                        updated on output
+            VA      -   value of the element being added
+            VB      -   value of the tag
+
+          -- ALGLIB --
+             Copyright 28.02.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void tagheappushi(ref double[] a,
+            ref int[] b,
+            ref int n,
+            double va,
+            int vb)
+        {
+            int j = 0;
+            int k = 0;
+            double v = 0;
+
+            if( n<0 )
+            {
+                return;
+            }
+            
+            //
+            // N=0 is a special case
+            //
+            if( n==0 )
+            {
+                a[0] = va;
+                b[0] = vb;
+                n = n+1;
+                return;
+            }
+            
+            //
+            // add current point to the heap
+            // (add to the bottom, then move up)
+            //
+            // we don't write point to the heap
+            // until its final position is determined
+            // (it allow us to reduce number of array access operations)
+            //
+            j = n;
+            n = n+1;
+            while( j>0 )
+            {
+                k = (j-1)/2;
+                v = a[k];
+                if( (double)(v)<(double)(va) )
+                {
+                    
+                    //
+                    // swap with higher element
+                    //
+                    a[j] = v;
+                    b[j] = b[k];
+                    j = k;
+                }
+                else
+                {
+                    
+                    //
+                    // element in its place. terminate.
+                    //
+                    break;
+                }
+            }
+            a[j] = va;
+            b[j] = vb;
+        }
+
+
+        /*************************************************************************
+        Heap operations: replaces top element with new element
+        (which is moved down)
+
+        PARAMETERS:
+            A       -   heap itself, must be at least array[0..N-1]
+            B       -   array of integer tags, which are updated according to
+                        permutations in the heap
+            N       -   size of the heap
+            VA      -   value of the element which replaces top element
+            VB      -   value of the tag
+
+          -- ALGLIB --
+             Copyright 28.02.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void tagheapreplacetopi(ref double[] a,
+            ref int[] b,
+            int n,
+            double va,
+            int vb)
+        {
+            int j = 0;
+            int k1 = 0;
+            int k2 = 0;
+            double v = 0;
+            double v1 = 0;
+            double v2 = 0;
+
+            if( n<1 )
+            {
+                return;
+            }
+            
+            //
+            // N=1 is a special case
+            //
+            if( n==1 )
+            {
+                a[0] = va;
+                b[0] = vb;
+                return;
+            }
+            
+            //
+            // move down through heap:
+            // * J  -   current element
+            // * K1 -   first child (always exists)
+            // * K2 -   second child (may not exists)
+            //
+            // we don't write point to the heap
+            // until its final position is determined
+            // (it allow us to reduce number of array access operations)
+            //
+            j = 0;
+            k1 = 1;
+            k2 = 2;
+            while( k1<n )
+            {
+                if( k2>=n )
+                {
+                    
+                    //
+                    // only one child.
+                    //
+                    // swap and terminate (because this child
+                    // have no siblings due to heap structure)
+                    //
+                    v = a[k1];
+                    if( (double)(v)>(double)(va) )
+                    {
+                        a[j] = v;
+                        b[j] = b[k1];
+                        j = k1;
+                    }
+                    break;
+                }
+                else
+                {
+                    
+                    //
+                    // two childs
+                    //
+                    v1 = a[k1];
+                    v2 = a[k2];
+                    if( (double)(v1)>(double)(v2) )
+                    {
+                        if( (double)(va)<(double)(v1) )
+                        {
+                            a[j] = v1;
+                            b[j] = b[k1];
+                            j = k1;
+                        }
+                        else
+                        {
+                            break;
+                        }
+                    }
+                    else
+                    {
+                        if( (double)(va)<(double)(v2) )
+                        {
+                            a[j] = v2;
+                            b[j] = b[k2];
+                            j = k2;
+                        }
+                        else
+                        {
+                            break;
+                        }
+                    }
+                    k1 = 2*j+1;
+                    k2 = 2*j+2;
+                }
+            }
+            a[j] = va;
+            b[j] = vb;
+        }
+
+
+        /*************************************************************************
+        Heap operations: pops top element from the heap
+
+        PARAMETERS:
+            A       -   heap itself, must be at least array[0..N-1]
+            B       -   array of integer tags, which are updated according to
+                        permutations in the heap
+            N       -   size of the heap, N>=1
+
+        On output top element is moved to A[N-1], B[N-1], heap is reordered, N is
+        decreased by 1.
+
+          -- ALGLIB --
+             Copyright 28.02.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void tagheappopi(ref double[] a,
+            ref int[] b,
+            ref int n)
+        {
+            double va = 0;
+            int vb = 0;
+
+            if( n<1 )
+            {
+                return;
+            }
+            
+            //
+            // N=1 is a special case
+            //
+            if( n==1 )
+            {
+                n = 0;
+                return;
+            }
+            
+            //
+            // swap top element and last element,
+            // then reorder heap
+            //
+            va = a[n-1];
+            vb = b[n-1];
+            a[n-1] = a[0];
+            b[n-1] = b[0];
+            n = n-1;
+            tagheapreplacetopi(ref a, ref b, n, va, vb);
+        }
+
+
+        /*************************************************************************
+        Search first element less than T in sorted array.
+
+        PARAMETERS:
+            A - sorted array by ascending from 0 to N-1
+            N - number of elements in array
+            T - the desired element
+
+        RESULT:
+            The very first element's index, which isn't less than T.
+        In the case when there aren't such elements, returns N.
+        *************************************************************************/
+        public static int lowerbound(double[] a,
+            int n,
+            double t)
+        {
+            int result = 0;
+            int l = 0;
+            int half = 0;
+            int first = 0;
+            int middle = 0;
+
+            l = n;
+            first = 0;
+            while( l>0 )
+            {
+                half = l/2;
+                middle = first+half;
+                if( (double)(a[middle])<(double)(t) )
+                {
+                    first = middle+1;
+                    l = l-half-1;
+                }
+                else
+                {
+                    l = half;
+                }
+            }
+            result = first;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Search first element more than T in sorted array.
+
+        PARAMETERS:
+            A - sorted array by ascending from 0 to N-1
+            N - number of elements in array
+            T - the desired element
+
+            RESULT:
+            The very first element's index, which more than T.
+        In the case when there aren't such elements, returns N.
+        *************************************************************************/
+        public static int upperbound(double[] a,
+            int n,
+            double t)
+        {
+            int result = 0;
+            int l = 0;
+            int half = 0;
+            int first = 0;
+            int middle = 0;
+
+            l = n;
+            first = 0;
+            while( l>0 )
+            {
+                half = l/2;
+                middle = first+half;
+                if( (double)(t)<(double)(a[middle]) )
+                {
+                    l = half;
+                }
+                else
+                {
+                    first = middle+1;
+                    l = l-half-1;
+                }
+            }
+            result = first;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Internal TagSortFastI: sorts A[I1...I2] (both bounds are included),
+        applies same permutations to B.
+
+          -- ALGLIB --
+             Copyright 06.09.2010 by Bochkanov Sergey
+        *************************************************************************/
+        private static void tagsortfastirec(ref double[] a,
+            ref int[] b,
+            ref double[] bufa,
+            ref int[] bufb,
+            int i1,
+            int i2)
+        {
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int cntless = 0;
+            int cnteq = 0;
+            int cntgreater = 0;
+            double tmpr = 0;
+            int tmpi = 0;
+            double v0 = 0;
+            double v1 = 0;
+            double v2 = 0;
+            double vp = 0;
+
+            
+            //
+            // Fast exit
+            //
+            if( i2<=i1 )
+            {
+                return;
+            }
+            
+            //
+            // Non-recursive sort for small arrays
+            //
+            if( i2-i1<=16 )
+            {
+                for(j=i1+1; j<=i2; j++)
+                {
+                    
+                    //
+                    // Search elements [I1..J-1] for place to insert Jth element.
+                    //
+                    // This code stops immediately if we can leave A[J] at J-th position
+                    // (all elements have same value of A[J] larger than any of them)
+                    //
+                    tmpr = a[j];
+                    tmpi = j;
+                    for(k=j-1; k>=i1; k--)
+                    {
+                        if( a[k]<=tmpr )
+                        {
+                            break;
+                        }
+                        tmpi = k;
+                    }
+                    k = tmpi;
+                    
+                    //
+                    // Insert Jth element into Kth position
+                    //
+                    if( k!=j )
+                    {
+                        tmpr = a[j];
+                        tmpi = b[j];
+                        for(i=j-1; i>=k; i--)
+                        {
+                            a[i+1] = a[i];
+                            b[i+1] = b[i];
+                        }
+                        a[k] = tmpr;
+                        b[k] = tmpi;
+                    }
+                }
+                return;
+            }
+            
+            //
+            // Quicksort: choose pivot
+            // Here we assume that I2-I1>=2
+            //
+            v0 = a[i1];
+            v1 = a[i1+(i2-i1)/2];
+            v2 = a[i2];
+            if( v0>v1 )
+            {
+                tmpr = v1;
+                v1 = v0;
+                v0 = tmpr;
+            }
+            if( v1>v2 )
+            {
+                tmpr = v2;
+                v2 = v1;
+                v1 = tmpr;
+            }
+            if( v0>v1 )
+            {
+                tmpr = v1;
+                v1 = v0;
+                v0 = tmpr;
+            }
+            vp = v1;
+            
+            //
+            // now pass through A/B and:
+            // * move elements that are LESS than VP to the left of A/B
+            // * move elements that are EQUAL to VP to the right of BufA/BufB (in the reverse order)
+            // * move elements that are GREATER than VP to the left of BufA/BufB (in the normal order
+            // * move elements from the tail of BufA/BufB to the middle of A/B (restoring normal order)
+            // * move elements from the left of BufA/BufB to the end of A/B
+            //
+            cntless = 0;
+            cnteq = 0;
+            cntgreater = 0;
+            for(i=i1; i<=i2; i++)
+            {
+                v0 = a[i];
+                if( v0<vp )
+                {
+                    
+                    //
+                    // LESS
+                    //
+                    k = i1+cntless;
+                    if( i!=k )
+                    {
+                        a[k] = v0;
+                        b[k] = b[i];
+                    }
+                    cntless = cntless+1;
+                    continue;
+                }
+                if( v0==vp )
+                {
+                    
+                    //
+                    // EQUAL
+                    //
+                    k = i2-cnteq;
+                    bufa[k] = v0;
+                    bufb[k] = b[i];
+                    cnteq = cnteq+1;
+                    continue;
+                }
+                
+                //
+                // GREATER
+                //
+                k = i1+cntgreater;
+                bufa[k] = v0;
+                bufb[k] = b[i];
+                cntgreater = cntgreater+1;
+            }
+            for(i=0; i<=cnteq-1; i++)
+            {
+                j = i1+cntless+cnteq-1-i;
+                k = i2+i-(cnteq-1);
+                a[j] = bufa[k];
+                b[j] = bufb[k];
+            }
+            for(i=0; i<=cntgreater-1; i++)
+            {
+                j = i1+cntless+cnteq+i;
+                k = i1+i;
+                a[j] = bufa[k];
+                b[j] = bufb[k];
+            }
+            
+            //
+            // Sort left and right parts of the array (ignoring middle part)
+            //
+            tagsortfastirec(ref a, ref b, ref bufa, ref bufb, i1, i1+cntless-1);
+            tagsortfastirec(ref a, ref b, ref bufa, ref bufb, i1+cntless+cnteq, i2);
+        }
+
+
+        /*************************************************************************
+        Internal TagSortFastR: sorts A[I1...I2] (both bounds are included),
+        applies same permutations to B.
+
+          -- ALGLIB --
+             Copyright 06.09.2010 by Bochkanov Sergey
+        *************************************************************************/
+        private static void tagsortfastrrec(ref double[] a,
+            ref double[] b,
+            ref double[] bufa,
+            ref double[] bufb,
+            int i1,
+            int i2)
+        {
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            double tmpr = 0;
+            double tmpr2 = 0;
+            int tmpi = 0;
+            int cntless = 0;
+            int cnteq = 0;
+            int cntgreater = 0;
+            double v0 = 0;
+            double v1 = 0;
+            double v2 = 0;
+            double vp = 0;
+
+            
+            //
+            // Fast exit
+            //
+            if( i2<=i1 )
+            {
+                return;
+            }
+            
+            //
+            // Non-recursive sort for small arrays
+            //
+            if( i2-i1<=16 )
+            {
+                for(j=i1+1; j<=i2; j++)
+                {
+                    
+                    //
+                    // Search elements [I1..J-1] for place to insert Jth element.
+                    //
+                    // This code stops immediatly if we can leave A[J] at J-th position
+                    // (all elements have same value of A[J] larger than any of them)
+                    //
+                    tmpr = a[j];
+                    tmpi = j;
+                    for(k=j-1; k>=i1; k--)
+                    {
+                        if( a[k]<=tmpr )
+                        {
+                            break;
+                        }
+                        tmpi = k;
+                    }
+                    k = tmpi;
+                    
+                    //
+                    // Insert Jth element into Kth position
+                    //
+                    if( k!=j )
+                    {
+                        tmpr = a[j];
+                        tmpr2 = b[j];
+                        for(i=j-1; i>=k; i--)
+                        {
+                            a[i+1] = a[i];
+                            b[i+1] = b[i];
+                        }
+                        a[k] = tmpr;
+                        b[k] = tmpr2;
+                    }
+                }
+                return;
+            }
+            
+            //
+            // Quicksort: choose pivot
+            // Here we assume that I2-I1>=16
+            //
+            v0 = a[i1];
+            v1 = a[i1+(i2-i1)/2];
+            v2 = a[i2];
+            if( v0>v1 )
+            {
+                tmpr = v1;
+                v1 = v0;
+                v0 = tmpr;
+            }
+            if( v1>v2 )
+            {
+                tmpr = v2;
+                v2 = v1;
+                v1 = tmpr;
+            }
+            if( v0>v1 )
+            {
+                tmpr = v1;
+                v1 = v0;
+                v0 = tmpr;
+            }
+            vp = v1;
+            
+            //
+            // now pass through A/B and:
+            // * move elements that are LESS than VP to the left of A/B
+            // * move elements that are EQUAL to VP to the right of BufA/BufB (in the reverse order)
+            // * move elements that are GREATER than VP to the left of BufA/BufB (in the normal order
+            // * move elements from the tail of BufA/BufB to the middle of A/B (restoring normal order)
+            // * move elements from the left of BufA/BufB to the end of A/B
+            //
+            cntless = 0;
+            cnteq = 0;
+            cntgreater = 0;
+            for(i=i1; i<=i2; i++)
+            {
+                v0 = a[i];
+                if( v0<vp )
+                {
+                    
+                    //
+                    // LESS
+                    //
+                    k = i1+cntless;
+                    if( i!=k )
+                    {
+                        a[k] = v0;
+                        b[k] = b[i];
+                    }
+                    cntless = cntless+1;
+                    continue;
+                }
+                if( v0==vp )
+                {
+                    
+                    //
+                    // EQUAL
+                    //
+                    k = i2-cnteq;
+                    bufa[k] = v0;
+                    bufb[k] = b[i];
+                    cnteq = cnteq+1;
+                    continue;
+                }
+                
+                //
+                // GREATER
+                //
+                k = i1+cntgreater;
+                bufa[k] = v0;
+                bufb[k] = b[i];
+                cntgreater = cntgreater+1;
+            }
+            for(i=0; i<=cnteq-1; i++)
+            {
+                j = i1+cntless+cnteq-1-i;
+                k = i2+i-(cnteq-1);
+                a[j] = bufa[k];
+                b[j] = bufb[k];
+            }
+            for(i=0; i<=cntgreater-1; i++)
+            {
+                j = i1+cntless+cnteq+i;
+                k = i1+i;
+                a[j] = bufa[k];
+                b[j] = bufb[k];
+            }
+            
+            //
+            // Sort left and right parts of the array (ignoring middle part)
+            //
+            tagsortfastrrec(ref a, ref b, ref bufa, ref bufb, i1, i1+cntless-1);
+            tagsortfastrrec(ref a, ref b, ref bufa, ref bufb, i1+cntless+cnteq, i2);
+        }
+
+
+        /*************************************************************************
+        Internal TagSortFastI: sorts A[I1...I2] (both bounds are included),
+        applies same permutations to B.
+
+          -- ALGLIB --
+             Copyright 06.09.2010 by Bochkanov Sergey
+        *************************************************************************/
+        private static void tagsortfastrec(ref double[] a,
+            ref double[] bufa,
+            int i1,
+            int i2)
+        {
+            int cntless = 0;
+            int cnteq = 0;
+            int cntgreater = 0;
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            double tmpr = 0;
+            int tmpi = 0;
+            double v0 = 0;
+            double v1 = 0;
+            double v2 = 0;
+            double vp = 0;
+
+            
+            //
+            // Fast exit
+            //
+            if( i2<=i1 )
+            {
+                return;
+            }
+            
+            //
+            // Non-recursive sort for small arrays
+            //
+            if( i2-i1<=16 )
+            {
+                for(j=i1+1; j<=i2; j++)
+                {
+                    
+                    //
+                    // Search elements [I1..J-1] for place to insert Jth element.
+                    //
+                    // This code stops immediatly if we can leave A[J] at J-th position
+                    // (all elements have same value of A[J] larger than any of them)
+                    //
+                    tmpr = a[j];
+                    tmpi = j;
+                    for(k=j-1; k>=i1; k--)
+                    {
+                        if( a[k]<=tmpr )
+                        {
+                            break;
+                        }
+                        tmpi = k;
+                    }
+                    k = tmpi;
+                    
+                    //
+                    // Insert Jth element into Kth position
+                    //
+                    if( k!=j )
+                    {
+                        tmpr = a[j];
+                        for(i=j-1; i>=k; i--)
+                        {
+                            a[i+1] = a[i];
+                        }
+                        a[k] = tmpr;
+                    }
+                }
+                return;
+            }
+            
+            //
+            // Quicksort: choose pivot
+            // Here we assume that I2-I1>=16
+            //
+            v0 = a[i1];
+            v1 = a[i1+(i2-i1)/2];
+            v2 = a[i2];
+            if( v0>v1 )
+            {
+                tmpr = v1;
+                v1 = v0;
+                v0 = tmpr;
+            }
+            if( v1>v2 )
+            {
+                tmpr = v2;
+                v2 = v1;
+                v1 = tmpr;
+            }
+            if( v0>v1 )
+            {
+                tmpr = v1;
+                v1 = v0;
+                v0 = tmpr;
+            }
+            vp = v1;
+            
+            //
+            // now pass through A/B and:
+            // * move elements that are LESS than VP to the left of A/B
+            // * move elements that are EQUAL to VP to the right of BufA/BufB (in the reverse order)
+            // * move elements that are GREATER than VP to the left of BufA/BufB (in the normal order
+            // * move elements from the tail of BufA/BufB to the middle of A/B (restoring normal order)
+            // * move elements from the left of BufA/BufB to the end of A/B
+            //
+            cntless = 0;
+            cnteq = 0;
+            cntgreater = 0;
+            for(i=i1; i<=i2; i++)
+            {
+                v0 = a[i];
+                if( v0<vp )
+                {
+                    
+                    //
+                    // LESS
+                    //
+                    k = i1+cntless;
+                    if( i!=k )
+                    {
+                        a[k] = v0;
+                    }
+                    cntless = cntless+1;
+                    continue;
+                }
+                if( v0==vp )
+                {
+                    
+                    //
+                    // EQUAL
+                    //
+                    k = i2-cnteq;
+                    bufa[k] = v0;
+                    cnteq = cnteq+1;
+                    continue;
+                }
+                
+                //
+                // GREATER
+                //
+                k = i1+cntgreater;
+                bufa[k] = v0;
+                cntgreater = cntgreater+1;
+            }
+            for(i=0; i<=cnteq-1; i++)
+            {
+                j = i1+cntless+cnteq-1-i;
+                k = i2+i-(cnteq-1);
+                a[j] = bufa[k];
+            }
+            for(i=0; i<=cntgreater-1; i++)
+            {
+                j = i1+cntless+cnteq+i;
+                k = i1+i;
+                a[j] = bufa[k];
+            }
+            
+            //
+            // Sort left and right parts of the array (ignoring middle part)
+            //
+            tagsortfastrec(ref a, ref bufa, i1, i1+cntless-1);
+            tagsortfastrec(ref a, ref bufa, i1+cntless+cnteq, i2);
+        }
+
+
+    }
+    public class basicstatops
+    {
+        /*************************************************************************
+        Internal ranking subroutine
+        *************************************************************************/
+        public static void rankx(ref double[] x,
+            int n,
+            apserv.apbuffers buf)
+        {
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int t = 0;
+            double tmp = 0;
+            int tmpi = 0;
+
+            
+            //
+            // Prepare
+            //
+            if( n<1 )
+            {
+                return;
+            }
+            if( n==1 )
+            {
+                x[0] = 1;
+                return;
+            }
+            if( alglib.ap.len(buf.ra1)<n )
+            {
+                buf.ra1 = new double[n];
+            }
+            if( alglib.ap.len(buf.ia1)<n )
+            {
+                buf.ia1 = new int[n];
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                buf.ra1[i] = x[i];
+                buf.ia1[i] = i;
+            }
+            
+            //
+            // sort {R, C}
+            //
+            if( n!=1 )
+            {
+                i = 2;
+                do
+                {
+                    t = i;
+                    while( t!=1 )
+                    {
+                        k = t/2;
+                        if( (double)(buf.ra1[k-1])>=(double)(buf.ra1[t-1]) )
+                        {
+                            t = 1;
+                        }
+                        else
+                        {
+                            tmp = buf.ra1[k-1];
+                            buf.ra1[k-1] = buf.ra1[t-1];
+                            buf.ra1[t-1] = tmp;
+                            tmpi = buf.ia1[k-1];
+                            buf.ia1[k-1] = buf.ia1[t-1];
+                            buf.ia1[t-1] = tmpi;
+                            t = k;
+                        }
+                    }
+                    i = i+1;
+                }
+                while( i<=n );
+                i = n-1;
+                do
+                {
+                    tmp = buf.ra1[i];
+                    buf.ra1[i] = buf.ra1[0];
+                    buf.ra1[0] = tmp;
+                    tmpi = buf.ia1[i];
+                    buf.ia1[i] = buf.ia1[0];
+                    buf.ia1[0] = tmpi;
+                    t = 1;
+                    while( t!=0 )
+                    {
+                        k = 2*t;
+                        if( k>i )
+                        {
+                            t = 0;
+                        }
+                        else
+                        {
+                            if( k<i )
+                            {
+                                if( (double)(buf.ra1[k])>(double)(buf.ra1[k-1]) )
+                                {
+                                    k = k+1;
+                                }
+                            }
+                            if( (double)(buf.ra1[t-1])>=(double)(buf.ra1[k-1]) )
+                            {
+                                t = 0;
+                            }
+                            else
+                            {
+                                tmp = buf.ra1[k-1];
+                                buf.ra1[k-1] = buf.ra1[t-1];
+                                buf.ra1[t-1] = tmp;
+                                tmpi = buf.ia1[k-1];
+                                buf.ia1[k-1] = buf.ia1[t-1];
+                                buf.ia1[t-1] = tmpi;
+                                t = k;
+                            }
+                        }
+                    }
+                    i = i-1;
+                }
+                while( i>=1 );
+            }
+            
+            //
+            // compute tied ranks
+            //
+            i = 0;
+            while( i<=n-1 )
+            {
+                j = i+1;
+                while( j<=n-1 )
+                {
+                    if( (double)(buf.ra1[j])!=(double)(buf.ra1[i]) )
+                    {
+                        break;
+                    }
+                    j = j+1;
+                }
+                for(k=i; k<=j-1; k++)
+                {
+                    buf.ra1[k] = 1+(double)(i+j-1)/(double)2;
+                }
+                i = j;
+            }
+            
+            //
+            // back to x
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                x[buf.ia1[i]] = buf.ra1[i];
+            }
+        }
+
+
+    }
+    public class ablasf
+    {
+        /*************************************************************************
+        Fast kernel
+
+          -- ALGLIB routine --
+             19.01.2010
+             Bochkanov Sergey
+        *************************************************************************/
+        public static bool cmatrixrank1f(int m,
+            int n,
+            ref complex[,] a,
+            int ia,
+            int ja,
+            ref complex[] u,
+            int iu,
+            ref complex[] v,
+            int iv)
+        {
+            bool result = new bool();
+
+            result = false;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Fast kernel
+
+          -- ALGLIB routine --
+             19.01.2010
+             Bochkanov Sergey
+        *************************************************************************/
+        public static bool rmatrixrank1f(int m,
+            int n,
+            ref double[,] a,
+            int ia,
+            int ja,
+            ref double[] u,
+            int iu,
+            ref double[] v,
+            int iv)
+        {
+            bool result = new bool();
+
+            result = false;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Fast kernel
+
+          -- ALGLIB routine --
+             19.01.2010
+             Bochkanov Sergey
+        *************************************************************************/
+        public static bool cmatrixmvf(int m,
+            int n,
+            complex[,] a,
+            int ia,
+            int ja,
+            int opa,
+            complex[] x,
+            int ix,
+            ref complex[] y,
+            int iy)
+        {
+            bool result = new bool();
+
+            result = false;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Fast kernel
+
+          -- ALGLIB routine --
+             19.01.2010
+             Bochkanov Sergey
+        *************************************************************************/
+        public static bool rmatrixmvf(int m,
+            int n,
+            double[,] a,
+            int ia,
+            int ja,
+            int opa,
+            double[] x,
+            int ix,
+            ref double[] y,
+            int iy)
+        {
+            bool result = new bool();
+
+            result = false;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Fast kernel
+
+          -- ALGLIB routine --
+             19.01.2010
+             Bochkanov Sergey
+        *************************************************************************/
+        public static bool cmatrixrighttrsmf(int m,
+            int n,
+            complex[,] a,
+            int i1,
+            int j1,
+            bool isupper,
+            bool isunit,
+            int optype,
+            ref complex[,] x,
+            int i2,
+            int j2)
+        {
+            bool result = new bool();
+
+            result = false;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Fast kernel
+
+          -- ALGLIB routine --
+             19.01.2010
+             Bochkanov Sergey
+        *************************************************************************/
+        public static bool cmatrixlefttrsmf(int m,
+            int n,
+            complex[,] a,
+            int i1,
+            int j1,
+            bool isupper,
+            bool isunit,
+            int optype,
+            ref complex[,] x,
+            int i2,
+            int j2)
+        {
+            bool result = new bool();
+
+            result = false;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Fast kernel
+
+          -- ALGLIB routine --
+             19.01.2010
+             Bochkanov Sergey
+        *************************************************************************/
+        public static bool rmatrixrighttrsmf(int m,
+            int n,
+            double[,] a,
+            int i1,
+            int j1,
+            bool isupper,
+            bool isunit,
+            int optype,
+            ref double[,] x,
+            int i2,
+            int j2)
+        {
+            bool result = new bool();
+
+            result = false;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Fast kernel
+
+          -- ALGLIB routine --
+             19.01.2010
+             Bochkanov Sergey
+        *************************************************************************/
+        public static bool rmatrixlefttrsmf(int m,
+            int n,
+            double[,] a,
+            int i1,
+            int j1,
+            bool isupper,
+            bool isunit,
+            int optype,
+            ref double[,] x,
+            int i2,
+            int j2)
+        {
+            bool result = new bool();
+
+            result = false;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Fast kernel
+
+          -- ALGLIB routine --
+             19.01.2010
+             Bochkanov Sergey
+        *************************************************************************/
+        public static bool cmatrixsyrkf(int n,
+            int k,
+            double alpha,
+            complex[,] a,
+            int ia,
+            int ja,
+            int optypea,
+            double beta,
+            ref complex[,] c,
+            int ic,
+            int jc,
+            bool isupper)
+        {
+            bool result = new bool();
+
+            result = false;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Fast kernel
+
+          -- ALGLIB routine --
+             19.01.2010
+             Bochkanov Sergey
+        *************************************************************************/
+        public static bool rmatrixsyrkf(int n,
+            int k,
+            double alpha,
+            double[,] a,
+            int ia,
+            int ja,
+            int optypea,
+            double beta,
+            ref double[,] c,
+            int ic,
+            int jc,
+            bool isupper)
+        {
+            bool result = new bool();
+
+            result = false;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Fast kernel
+
+          -- ALGLIB routine --
+             19.01.2010
+             Bochkanov Sergey
+        *************************************************************************/
+        public static bool rmatrixgemmf(int m,
+            int n,
+            int k,
+            double alpha,
+            double[,] a,
+            int ia,
+            int ja,
+            int optypea,
+            double[,] b,
+            int ib,
+            int jb,
+            int optypeb,
+            double beta,
+            ref double[,] c,
+            int ic,
+            int jc)
+        {
+            bool result = new bool();
+
+            result = false;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Fast kernel
+
+          -- ALGLIB routine --
+             19.01.2010
+             Bochkanov Sergey
+        *************************************************************************/
+        public static bool cmatrixgemmf(int m,
+            int n,
+            int k,
+            complex alpha,
+            complex[,] a,
+            int ia,
+            int ja,
+            int optypea,
+            complex[,] b,
+            int ib,
+            int jb,
+            int optypeb,
+            complex beta,
+            ref complex[,] c,
+            int ic,
+            int jc)
+        {
+            bool result = new bool();
+
+            result = false;
+            return result;
+        }
+
+
+    }
+    public class hblas
+    {
+        public static void hermitianmatrixvectormultiply(complex[,] a,
+            bool isupper,
+            int i1,
+            int i2,
+            complex[] x,
+            complex alpha,
+            ref complex[] y)
+        {
+            int i = 0;
+            int ba1 = 0;
+            int ba2 = 0;
+            int by1 = 0;
+            int by2 = 0;
+            int bx1 = 0;
+            int bx2 = 0;
+            int n = 0;
+            complex v = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            n = i2-i1+1;
+            if( n<=0 )
+            {
+                return;
+            }
+            
+            //
+            // Let A = L + D + U, where
+            //  L is strictly lower triangular (main diagonal is zero)
+            //  D is diagonal
+            //  U is strictly upper triangular (main diagonal is zero)
+            //
+            // A*x = L*x + D*x + U*x
+            //
+            // Calculate D*x first
+            //
+            for(i=i1; i<=i2; i++)
+            {
+                y[i-i1+1] = a[i,i]*x[i-i1+1];
+            }
+            
+            //
+            // Add L*x + U*x
+            //
+            if( isupper )
+            {
+                for(i=i1; i<=i2-1; i++)
+                {
+                    
+                    //
+                    // Add L*x to the result
+                    //
+                    v = x[i-i1+1];
+                    by1 = i-i1+2;
+                    by2 = n;
+                    ba1 = i+1;
+                    ba2 = i2;
+                    i1_ = (ba1) - (by1);
+                    for(i_=by1; i_<=by2;i_++)
+                    {
+                        y[i_] = y[i_] + v*math.conj(a[i,i_+i1_]);
+                    }
+                    
+                    //
+                    // Add U*x to the result
+                    //
+                    bx1 = i-i1+2;
+                    bx2 = n;
+                    ba1 = i+1;
+                    ba2 = i2;
+                    i1_ = (ba1)-(bx1);
+                    v = 0.0;
+                    for(i_=bx1; i_<=bx2;i_++)
+                    {
+                        v += x[i_]*a[i,i_+i1_];
+                    }
+                    y[i-i1+1] = y[i-i1+1]+v;
+                }
+            }
+            else
+            {
+                for(i=i1+1; i<=i2; i++)
+                {
+                    
+                    //
+                    // Add L*x to the result
+                    //
+                    bx1 = 1;
+                    bx2 = i-i1;
+                    ba1 = i1;
+                    ba2 = i-1;
+                    i1_ = (ba1)-(bx1);
+                    v = 0.0;
+                    for(i_=bx1; i_<=bx2;i_++)
+                    {
+                        v += x[i_]*a[i,i_+i1_];
+                    }
+                    y[i-i1+1] = y[i-i1+1]+v;
+                    
+                    //
+                    // Add U*x to the result
+                    //
+                    v = x[i-i1+1];
+                    by1 = 1;
+                    by2 = i-i1;
+                    ba1 = i1;
+                    ba2 = i-1;
+                    i1_ = (ba1) - (by1);
+                    for(i_=by1; i_<=by2;i_++)
+                    {
+                        y[i_] = y[i_] + v*math.conj(a[i,i_+i1_]);
+                    }
+                }
+            }
+            for(i_=1; i_<=n;i_++)
+            {
+                y[i_] = alpha*y[i_];
+            }
+        }
+
+
+        public static void hermitianrank2update(ref complex[,] a,
+            bool isupper,
+            int i1,
+            int i2,
+            complex[] x,
+            complex[] y,
+            ref complex[] t,
+            complex alpha)
+        {
+            int i = 0;
+            int tp1 = 0;
+            int tp2 = 0;
+            complex v = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            if( isupper )
+            {
+                for(i=i1; i<=i2; i++)
+                {
+                    tp1 = i+1-i1;
+                    tp2 = i2-i1+1;
+                    v = alpha*x[i+1-i1];
+                    for(i_=tp1; i_<=tp2;i_++)
+                    {
+                        t[i_] = v*math.conj(y[i_]);
+                    }
+                    v = math.conj(alpha)*y[i+1-i1];
+                    for(i_=tp1; i_<=tp2;i_++)
+                    {
+                        t[i_] = t[i_] + v*math.conj(x[i_]);
+                    }
+                    i1_ = (tp1) - (i);
+                    for(i_=i; i_<=i2;i_++)
+                    {
+                        a[i,i_] = a[i,i_] + t[i_+i1_];
+                    }
+                }
+            }
+            else
+            {
+                for(i=i1; i<=i2; i++)
+                {
+                    tp1 = 1;
+                    tp2 = i+1-i1;
+                    v = alpha*x[i+1-i1];
+                    for(i_=tp1; i_<=tp2;i_++)
+                    {
+                        t[i_] = v*math.conj(y[i_]);
+                    }
+                    v = math.conj(alpha)*y[i+1-i1];
+                    for(i_=tp1; i_<=tp2;i_++)
+                    {
+                        t[i_] = t[i_] + v*math.conj(x[i_]);
+                    }
+                    i1_ = (tp1) - (i1);
+                    for(i_=i1; i_<=i;i_++)
+                    {
+                        a[i,i_] = a[i,i_] + t[i_+i1_];
+                    }
+                }
+            }
+        }
+
+
+    }
+    public class reflections
+    {
+        /*************************************************************************
+        Generation of an elementary reflection transformation
+
+        The subroutine generates elementary reflection H of order N, so that, for
+        a given X, the following equality holds true:
+
+            ( X(1) )   ( Beta )
+        H * (  ..  ) = (  0   )
+            ( X(n) )   (  0   )
+
+        where
+                      ( V(1) )
+        H = 1 - Tau * (  ..  ) * ( V(1), ..., V(n) )
+                      ( V(n) )
+
+        where the first component of vector V equals 1.
+
+        Input parameters:
+            X   -   vector. Array whose index ranges within [1..N].
+            N   -   reflection order.
+
+        Output parameters:
+            X   -   components from 2 to N are replaced with vector V.
+                    The first component is replaced with parameter Beta.
+            Tau -   scalar value Tau. If X is a null vector, Tau equals 0,
+                    otherwise 1 <= Tau <= 2.
+
+        This subroutine is the modification of the DLARFG subroutines from
+        the LAPACK library.
+
+        MODIFICATIONS:
+            24.12.2005 sign(Alpha) was replaced with an analogous to the Fortran SIGN code.
+
+          -- LAPACK auxiliary routine (version 3.0) --
+             Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd.,
+             Courant Institute, Argonne National Lab, and Rice University
+             September 30, 1994
+        *************************************************************************/
+        public static void generatereflection(ref double[] x,
+            int n,
+            ref double tau)
+        {
+            int j = 0;
+            double alpha = 0;
+            double xnorm = 0;
+            double v = 0;
+            double beta = 0;
+            double mx = 0;
+            double s = 0;
+            int i_ = 0;
+
+            tau = 0;
+
+            if( n<=1 )
+            {
+                tau = 0;
+                return;
+            }
+            
+            //
+            // Scale if needed (to avoid overflow/underflow during intermediate
+            // calculations).
+            //
+            mx = 0;
+            for(j=1; j<=n; j++)
+            {
+                mx = Math.Max(Math.Abs(x[j]), mx);
+            }
+            s = 1;
+            if( (double)(mx)!=(double)(0) )
+            {
+                if( (double)(mx)<=(double)(math.minrealnumber/math.machineepsilon) )
+                {
+                    s = math.minrealnumber/math.machineepsilon;
+                    v = 1/s;
+                    for(i_=1; i_<=n;i_++)
+                    {
+                        x[i_] = v*x[i_];
+                    }
+                    mx = mx*v;
+                }
+                else
+                {
+                    if( (double)(mx)>=(double)(math.maxrealnumber*math.machineepsilon) )
+                    {
+                        s = math.maxrealnumber*math.machineepsilon;
+                        v = 1/s;
+                        for(i_=1; i_<=n;i_++)
+                        {
+                            x[i_] = v*x[i_];
+                        }
+                        mx = mx*v;
+                    }
+                }
+            }
+            
+            //
+            // XNORM = DNRM2( N-1, X, INCX )
+            //
+            alpha = x[1];
+            xnorm = 0;
+            if( (double)(mx)!=(double)(0) )
+            {
+                for(j=2; j<=n; j++)
+                {
+                    xnorm = xnorm+math.sqr(x[j]/mx);
+                }
+                xnorm = Math.Sqrt(xnorm)*mx;
+            }
+            if( (double)(xnorm)==(double)(0) )
+            {
+                
+                //
+                // H  =  I
+                //
+                tau = 0;
+                x[1] = x[1]*s;
+                return;
+            }
+            
+            //
+            // general case
+            //
+            mx = Math.Max(Math.Abs(alpha), Math.Abs(xnorm));
+            beta = -(mx*Math.Sqrt(math.sqr(alpha/mx)+math.sqr(xnorm/mx)));
+            if( (double)(alpha)<(double)(0) )
+            {
+                beta = -beta;
+            }
+            tau = (beta-alpha)/beta;
+            v = 1/(alpha-beta);
+            for(i_=2; i_<=n;i_++)
+            {
+                x[i_] = v*x[i_];
+            }
+            x[1] = beta;
+            
+            //
+            // Scale back outputs
+            //
+            x[1] = x[1]*s;
+        }
+
+
+        /*************************************************************************
+        Application of an elementary reflection to a rectangular matrix of size MxN
+
+        The algorithm pre-multiplies the matrix by an elementary reflection transformation
+        which is given by column V and scalar Tau (see the description of the
+        GenerateReflection procedure). Not the whole matrix but only a part of it
+        is transformed (rows from M1 to M2, columns from N1 to N2). Only the elements
+        of this submatrix are changed.
+
+        Input parameters:
+            C       -   matrix to be transformed.
+            Tau     -   scalar defining the transformation.
+            V       -   column defining the transformation.
+                        Array whose index ranges within [1..M2-M1+1].
+            M1, M2  -   range of rows to be transformed.
+            N1, N2  -   range of columns to be transformed.
+            WORK    -   working array whose indexes goes from N1 to N2.
+
+        Output parameters:
+            C       -   the result of multiplying the input matrix C by the
+                        transformation matrix which is given by Tau and V.
+                        If N1>N2 or M1>M2, C is not modified.
+
+          -- LAPACK auxiliary routine (version 3.0) --
+             Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd.,
+             Courant Institute, Argonne National Lab, and Rice University
+             September 30, 1994
+        *************************************************************************/
+        public static void applyreflectionfromtheleft(ref double[,] c,
+            double tau,
+            double[] v,
+            int m1,
+            int m2,
+            int n1,
+            int n2,
+            ref double[] work)
+        {
+            double t = 0;
+            int i = 0;
+            int vm = 0;
+            int i_ = 0;
+
+            if( ((double)(tau)==(double)(0) || n1>n2) || m1>m2 )
+            {
+                return;
+            }
+            
+            //
+            // w := C' * v
+            //
+            vm = m2-m1+1;
+            for(i=n1; i<=n2; i++)
+            {
+                work[i] = 0;
+            }
+            for(i=m1; i<=m2; i++)
+            {
+                t = v[i+1-m1];
+                for(i_=n1; i_<=n2;i_++)
+                {
+                    work[i_] = work[i_] + t*c[i,i_];
+                }
+            }
+            
+            //
+            // C := C - tau * v * w'
+            //
+            for(i=m1; i<=m2; i++)
+            {
+                t = v[i-m1+1]*tau;
+                for(i_=n1; i_<=n2;i_++)
+                {
+                    c[i,i_] = c[i,i_] - t*work[i_];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Application of an elementary reflection to a rectangular matrix of size MxN
+
+        The algorithm post-multiplies the matrix by an elementary reflection transformation
+        which is given by column V and scalar Tau (see the description of the
+        GenerateReflection procedure). Not the whole matrix but only a part of it
+        is transformed (rows from M1 to M2, columns from N1 to N2). Only the
+        elements of this submatrix are changed.
+
+        Input parameters:
+            C       -   matrix to be transformed.
+            Tau     -   scalar defining the transformation.
+            V       -   column defining the transformation.
+                        Array whose index ranges within [1..N2-N1+1].
+            M1, M2  -   range of rows to be transformed.
+            N1, N2  -   range of columns to be transformed.
+            WORK    -   working array whose indexes goes from M1 to M2.
+
+        Output parameters:
+            C       -   the result of multiplying the input matrix C by the
+                        transformation matrix which is given by Tau and V.
+                        If N1>N2 or M1>M2, C is not modified.
+
+          -- LAPACK auxiliary routine (version 3.0) --
+             Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd.,
+             Courant Institute, Argonne National Lab, and Rice University
+             September 30, 1994
+        *************************************************************************/
+        public static void applyreflectionfromtheright(ref double[,] c,
+            double tau,
+            double[] v,
+            int m1,
+            int m2,
+            int n1,
+            int n2,
+            ref double[] work)
+        {
+            double t = 0;
+            int i = 0;
+            int vm = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            if( ((double)(tau)==(double)(0) || n1>n2) || m1>m2 )
+            {
+                return;
+            }
+            vm = n2-n1+1;
+            for(i=m1; i<=m2; i++)
+            {
+                i1_ = (1)-(n1);
+                t = 0.0;
+                for(i_=n1; i_<=n2;i_++)
+                {
+                    t += c[i,i_]*v[i_+i1_];
+                }
+                t = t*tau;
+                i1_ = (1) - (n1);
+                for(i_=n1; i_<=n2;i_++)
+                {
+                    c[i,i_] = c[i,i_] - t*v[i_+i1_];
+                }
+            }
+        }
+
+
+    }
+    public class creflections
+    {
+        /*************************************************************************
+        Generation of an elementary complex reflection transformation
+
+        The subroutine generates elementary complex reflection H of  order  N,  so
+        that, for a given X, the following equality holds true:
+
+             ( X(1) )   ( Beta )
+        H' * (  ..  ) = (  0   ),   H'*H = I,   Beta is a real number
+             ( X(n) )   (  0   )
+
+        where
+
+                      ( V(1) )
+        H = 1 - Tau * (  ..  ) * ( conj(V(1)), ..., conj(V(n)) )
+                      ( V(n) )
+
+        where the first component of vector V equals 1.
+
+        Input parameters:
+            X   -   vector. Array with elements [1..N].
+            N   -   reflection order.
+
+        Output parameters:
+            X   -   components from 2 to N are replaced by vector V.
+                    The first component is replaced with parameter Beta.
+            Tau -   scalar value Tau.
+
+        This subroutine is the modification of CLARFG subroutines  from the LAPACK
+        library. It has similar functionality except for the fact that it  doesnt
+        handle errors when intermediate results cause an overflow.
+
+          -- LAPACK auxiliary routine (version 3.0) --
+             Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd.,
+             Courant Institute, Argonne National Lab, and Rice University
+             September 30, 1994
+        *************************************************************************/
+        public static void complexgeneratereflection(ref complex[] x,
+            int n,
+            ref complex tau)
+        {
+            int j = 0;
+            complex alpha = 0;
+            double alphi = 0;
+            double alphr = 0;
+            double beta = 0;
+            double xnorm = 0;
+            double mx = 0;
+            complex t = 0;
+            double s = 0;
+            complex v = 0;
+            int i_ = 0;
+
+            tau = 0;
+
+            if( n<=0 )
+            {
+                tau = 0;
+                return;
+            }
+            
+            //
+            // Scale if needed (to avoid overflow/underflow during intermediate
+            // calculations).
+            //
+            mx = 0;
+            for(j=1; j<=n; j++)
+            {
+                mx = Math.Max(math.abscomplex(x[j]), mx);
+            }
+            s = 1;
+            if( (double)(mx)!=(double)(0) )
+            {
+                if( (double)(mx)<(double)(1) )
+                {
+                    s = Math.Sqrt(math.minrealnumber);
+                    v = 1/s;
+                    for(i_=1; i_<=n;i_++)
+                    {
+                        x[i_] = v*x[i_];
+                    }
+                }
+                else
+                {
+                    s = Math.Sqrt(math.maxrealnumber);
+                    v = 1/s;
+                    for(i_=1; i_<=n;i_++)
+                    {
+                        x[i_] = v*x[i_];
+                    }
+                }
+            }
+            
+            //
+            // calculate
+            //
+            alpha = x[1];
+            mx = 0;
+            for(j=2; j<=n; j++)
+            {
+                mx = Math.Max(math.abscomplex(x[j]), mx);
+            }
+            xnorm = 0;
+            if( (double)(mx)!=(double)(0) )
+            {
+                for(j=2; j<=n; j++)
+                {
+                    t = x[j]/mx;
+                    xnorm = xnorm+(t*math.conj(t)).x;
+                }
+                xnorm = Math.Sqrt(xnorm)*mx;
+            }
+            alphr = alpha.x;
+            alphi = alpha.y;
+            if( (double)(xnorm)==(double)(0) && (double)(alphi)==(double)(0) )
+            {
+                tau = 0;
+                x[1] = x[1]*s;
+                return;
+            }
+            mx = Math.Max(Math.Abs(alphr), Math.Abs(alphi));
+            mx = Math.Max(mx, Math.Abs(xnorm));
+            beta = -(mx*Math.Sqrt(math.sqr(alphr/mx)+math.sqr(alphi/mx)+math.sqr(xnorm/mx)));
+            if( (double)(alphr)<(double)(0) )
+            {
+                beta = -beta;
+            }
+            tau.x = (beta-alphr)/beta;
+            tau.y = -(alphi/beta);
+            alpha = 1/(alpha-beta);
+            if( n>1 )
+            {
+                for(i_=2; i_<=n;i_++)
+                {
+                    x[i_] = alpha*x[i_];
+                }
+            }
+            alpha = beta;
+            x[1] = alpha;
+            
+            //
+            // Scale back
+            //
+            x[1] = x[1]*s;
+        }
+
+
+        /*************************************************************************
+        Application of an elementary reflection to a rectangular matrix of size MxN
+
+        The  algorithm  pre-multiplies  the  matrix  by  an  elementary reflection
+        transformation  which  is  given  by  column  V  and  scalar  Tau (see the
+        description of the GenerateReflection). Not the whole matrix  but  only  a
+        part of it is transformed (rows from M1 to M2, columns from N1 to N2). Only
+        the elements of this submatrix are changed.
+
+        Note: the matrix is multiplied by H, not by H'.   If  it  is  required  to
+        multiply the matrix by H', it is necessary to pass Conj(Tau) instead of Tau.
+
+        Input parameters:
+            C       -   matrix to be transformed.
+            Tau     -   scalar defining transformation.
+            V       -   column defining transformation.
+                        Array whose index ranges within [1..M2-M1+1]
+            M1, M2  -   range of rows to be transformed.
+            N1, N2  -   range of columns to be transformed.
+            WORK    -   working array whose index goes from N1 to N2.
+
+        Output parameters:
+            C       -   the result of multiplying the input matrix C by the
+                        transformation matrix which is given by Tau and V.
+                        If N1>N2 or M1>M2, C is not modified.
+
+          -- LAPACK auxiliary routine (version 3.0) --
+             Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd.,
+             Courant Institute, Argonne National Lab, and Rice University
+             September 30, 1994
+        *************************************************************************/
+        public static void complexapplyreflectionfromtheleft(ref complex[,] c,
+            complex tau,
+            complex[] v,
+            int m1,
+            int m2,
+            int n1,
+            int n2,
+            ref complex[] work)
+        {
+            complex t = 0;
+            int i = 0;
+            int vm = 0;
+            int i_ = 0;
+
+            if( (tau==0 || n1>n2) || m1>m2 )
+            {
+                return;
+            }
+            
+            //
+            // w := C^T * conj(v)
+            //
+            vm = m2-m1+1;
+            for(i=n1; i<=n2; i++)
+            {
+                work[i] = 0;
+            }
+            for(i=m1; i<=m2; i++)
+            {
+                t = math.conj(v[i+1-m1]);
+                for(i_=n1; i_<=n2;i_++)
+                {
+                    work[i_] = work[i_] + t*c[i,i_];
+                }
+            }
+            
+            //
+            // C := C - tau * v * w^T
+            //
+            for(i=m1; i<=m2; i++)
+            {
+                t = v[i-m1+1]*tau;
+                for(i_=n1; i_<=n2;i_++)
+                {
+                    c[i,i_] = c[i,i_] - t*work[i_];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Application of an elementary reflection to a rectangular matrix of size MxN
+
+        The  algorithm  post-multiplies  the  matrix  by  an elementary reflection
+        transformation  which  is  given  by  column  V  and  scalar  Tau (see the
+        description  of  the  GenerateReflection). Not the whole matrix but only a
+        part  of  it  is  transformed (rows from M1 to M2, columns from N1 to N2).
+        Only the elements of this submatrix are changed.
+
+        Input parameters:
+            C       -   matrix to be transformed.
+            Tau     -   scalar defining transformation.
+            V       -   column defining transformation.
+                        Array whose index ranges within [1..N2-N1+1]
+            M1, M2  -   range of rows to be transformed.
+            N1, N2  -   range of columns to be transformed.
+            WORK    -   working array whose index goes from M1 to M2.
+
+        Output parameters:
+            C       -   the result of multiplying the input matrix C by the
+                        transformation matrix which is given by Tau and V.
+                        If N1>N2 or M1>M2, C is not modified.
+
+          -- LAPACK auxiliary routine (version 3.0) --
+             Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd.,
+             Courant Institute, Argonne National Lab, and Rice University
+             September 30, 1994
+        *************************************************************************/
+        public static void complexapplyreflectionfromtheright(ref complex[,] c,
+            complex tau,
+            ref complex[] v,
+            int m1,
+            int m2,
+            int n1,
+            int n2,
+            ref complex[] work)
+        {
+            complex t = 0;
+            int i = 0;
+            int vm = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            if( (tau==0 || n1>n2) || m1>m2 )
+            {
+                return;
+            }
+            
+            //
+            // w := C * v
+            //
+            vm = n2-n1+1;
+            for(i=m1; i<=m2; i++)
+            {
+                i1_ = (1)-(n1);
+                t = 0.0;
+                for(i_=n1; i_<=n2;i_++)
+                {
+                    t += c[i,i_]*v[i_+i1_];
+                }
+                work[i] = t;
+            }
+            
+            //
+            // C := C - w * conj(v^T)
+            //
+            for(i_=1; i_<=vm;i_++)
+            {
+                v[i_] = math.conj(v[i_]);
+            }
+            for(i=m1; i<=m2; i++)
+            {
+                t = work[i]*tau;
+                i1_ = (1) - (n1);
+                for(i_=n1; i_<=n2;i_++)
+                {
+                    c[i,i_] = c[i,i_] - t*v[i_+i1_];
+                }
+            }
+            for(i_=1; i_<=vm;i_++)
+            {
+                v[i_] = math.conj(v[i_]);
+            }
+        }
+
+
+    }
+    public class sblas
+    {
+        public static void symmetricmatrixvectormultiply(double[,] a,
+            bool isupper,
+            int i1,
+            int i2,
+            double[] x,
+            double alpha,
+            ref double[] y)
+        {
+            int i = 0;
+            int ba1 = 0;
+            int ba2 = 0;
+            int by1 = 0;
+            int by2 = 0;
+            int bx1 = 0;
+            int bx2 = 0;
+            int n = 0;
+            double v = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            n = i2-i1+1;
+            if( n<=0 )
+            {
+                return;
+            }
+            
+            //
+            // Let A = L + D + U, where
+            //  L is strictly lower triangular (main diagonal is zero)
+            //  D is diagonal
+            //  U is strictly upper triangular (main diagonal is zero)
+            //
+            // A*x = L*x + D*x + U*x
+            //
+            // Calculate D*x first
+            //
+            for(i=i1; i<=i2; i++)
+            {
+                y[i-i1+1] = a[i,i]*x[i-i1+1];
+            }
+            
+            //
+            // Add L*x + U*x
+            //
+            if( isupper )
+            {
+                for(i=i1; i<=i2-1; i++)
+                {
+                    
+                    //
+                    // Add L*x to the result
+                    //
+                    v = x[i-i1+1];
+                    by1 = i-i1+2;
+                    by2 = n;
+                    ba1 = i+1;
+                    ba2 = i2;
+                    i1_ = (ba1) - (by1);
+                    for(i_=by1; i_<=by2;i_++)
+                    {
+                        y[i_] = y[i_] + v*a[i,i_+i1_];
+                    }
+                    
+                    //
+                    // Add U*x to the result
+                    //
+                    bx1 = i-i1+2;
+                    bx2 = n;
+                    ba1 = i+1;
+                    ba2 = i2;
+                    i1_ = (ba1)-(bx1);
+                    v = 0.0;
+                    for(i_=bx1; i_<=bx2;i_++)
+                    {
+                        v += x[i_]*a[i,i_+i1_];
+                    }
+                    y[i-i1+1] = y[i-i1+1]+v;
+                }
+            }
+            else
+            {
+                for(i=i1+1; i<=i2; i++)
+                {
+                    
+                    //
+                    // Add L*x to the result
+                    //
+                    bx1 = 1;
+                    bx2 = i-i1;
+                    ba1 = i1;
+                    ba2 = i-1;
+                    i1_ = (ba1)-(bx1);
+                    v = 0.0;
+                    for(i_=bx1; i_<=bx2;i_++)
+                    {
+                        v += x[i_]*a[i,i_+i1_];
+                    }
+                    y[i-i1+1] = y[i-i1+1]+v;
+                    
+                    //
+                    // Add U*x to the result
+                    //
+                    v = x[i-i1+1];
+                    by1 = 1;
+                    by2 = i-i1;
+                    ba1 = i1;
+                    ba2 = i-1;
+                    i1_ = (ba1) - (by1);
+                    for(i_=by1; i_<=by2;i_++)
+                    {
+                        y[i_] = y[i_] + v*a[i,i_+i1_];
+                    }
+                }
+            }
+            for(i_=1; i_<=n;i_++)
+            {
+                y[i_] = alpha*y[i_];
+            }
+        }
+
+
+        public static void symmetricrank2update(ref double[,] a,
+            bool isupper,
+            int i1,
+            int i2,
+            double[] x,
+            double[] y,
+            ref double[] t,
+            double alpha)
+        {
+            int i = 0;
+            int tp1 = 0;
+            int tp2 = 0;
+            double v = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            if( isupper )
+            {
+                for(i=i1; i<=i2; i++)
+                {
+                    tp1 = i+1-i1;
+                    tp2 = i2-i1+1;
+                    v = x[i+1-i1];
+                    for(i_=tp1; i_<=tp2;i_++)
+                    {
+                        t[i_] = v*y[i_];
+                    }
+                    v = y[i+1-i1];
+                    for(i_=tp1; i_<=tp2;i_++)
+                    {
+                        t[i_] = t[i_] + v*x[i_];
+                    }
+                    for(i_=tp1; i_<=tp2;i_++)
+                    {
+                        t[i_] = alpha*t[i_];
+                    }
+                    i1_ = (tp1) - (i);
+                    for(i_=i; i_<=i2;i_++)
+                    {
+                        a[i,i_] = a[i,i_] + t[i_+i1_];
+                    }
+                }
+            }
+            else
+            {
+                for(i=i1; i<=i2; i++)
+                {
+                    tp1 = 1;
+                    tp2 = i+1-i1;
+                    v = x[i+1-i1];
+                    for(i_=tp1; i_<=tp2;i_++)
+                    {
+                        t[i_] = v*y[i_];
+                    }
+                    v = y[i+1-i1];
+                    for(i_=tp1; i_<=tp2;i_++)
+                    {
+                        t[i_] = t[i_] + v*x[i_];
+                    }
+                    for(i_=tp1; i_<=tp2;i_++)
+                    {
+                        t[i_] = alpha*t[i_];
+                    }
+                    i1_ = (tp1) - (i1);
+                    for(i_=i1; i_<=i;i_++)
+                    {
+                        a[i,i_] = a[i,i_] + t[i_+i1_];
+                    }
+                }
+            }
+        }
+
+
+    }
+    public class blas
+    {
+        public static double vectornorm2(double[] x,
+            int i1,
+            int i2)
+        {
+            double result = 0;
+            int n = 0;
+            int ix = 0;
+            double absxi = 0;
+            double scl = 0;
+            double ssq = 0;
+
+            n = i2-i1+1;
+            if( n<1 )
+            {
+                result = 0;
+                return result;
+            }
+            if( n==1 )
+            {
+                result = Math.Abs(x[i1]);
+                return result;
+            }
+            scl = 0;
+            ssq = 1;
+            for(ix=i1; ix<=i2; ix++)
+            {
+                if( (double)(x[ix])!=(double)(0) )
+                {
+                    absxi = Math.Abs(x[ix]);
+                    if( (double)(scl)<(double)(absxi) )
+                    {
+                        ssq = 1+ssq*math.sqr(scl/absxi);
+                        scl = absxi;
+                    }
+                    else
+                    {
+                        ssq = ssq+math.sqr(absxi/scl);
+                    }
+                }
+            }
+            result = scl*Math.Sqrt(ssq);
+            return result;
+        }
+
+
+        public static int vectoridxabsmax(double[] x,
+            int i1,
+            int i2)
+        {
+            int result = 0;
+            int i = 0;
+            double a = 0;
+
+            result = i1;
+            a = Math.Abs(x[result]);
+            for(i=i1+1; i<=i2; i++)
+            {
+                if( (double)(Math.Abs(x[i]))>(double)(Math.Abs(x[result])) )
+                {
+                    result = i;
+                }
+            }
+            return result;
+        }
+
+
+        public static int columnidxabsmax(double[,] x,
+            int i1,
+            int i2,
+            int j)
+        {
+            int result = 0;
+            int i = 0;
+            double a = 0;
+
+            result = i1;
+            a = Math.Abs(x[result,j]);
+            for(i=i1+1; i<=i2; i++)
+            {
+                if( (double)(Math.Abs(x[i,j]))>(double)(Math.Abs(x[result,j])) )
+                {
+                    result = i;
+                }
+            }
+            return result;
+        }
+
+
+        public static int rowidxabsmax(double[,] x,
+            int j1,
+            int j2,
+            int i)
+        {
+            int result = 0;
+            int j = 0;
+            double a = 0;
+
+            result = j1;
+            a = Math.Abs(x[i,result]);
+            for(j=j1+1; j<=j2; j++)
+            {
+                if( (double)(Math.Abs(x[i,j]))>(double)(Math.Abs(x[i,result])) )
+                {
+                    result = j;
+                }
+            }
+            return result;
+        }
+
+
+        public static double upperhessenberg1norm(double[,] a,
+            int i1,
+            int i2,
+            int j1,
+            int j2,
+            ref double[] work)
+        {
+            double result = 0;
+            int i = 0;
+            int j = 0;
+
+            alglib.ap.assert(i2-i1==j2-j1, "UpperHessenberg1Norm: I2-I1<>J2-J1!");
+            for(j=j1; j<=j2; j++)
+            {
+                work[j] = 0;
+            }
+            for(i=i1; i<=i2; i++)
+            {
+                for(j=Math.Max(j1, j1+i-i1-1); j<=j2; j++)
+                {
+                    work[j] = work[j]+Math.Abs(a[i,j]);
+                }
+            }
+            result = 0;
+            for(j=j1; j<=j2; j++)
+            {
+                result = Math.Max(result, work[j]);
+            }
+            return result;
+        }
+
+
+        public static void copymatrix(double[,] a,
+            int is1,
+            int is2,
+            int js1,
+            int js2,
+            ref double[,] b,
+            int id1,
+            int id2,
+            int jd1,
+            int jd2)
+        {
+            int isrc = 0;
+            int idst = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            if( is1>is2 || js1>js2 )
+            {
+                return;
+            }
+            alglib.ap.assert(is2-is1==id2-id1, "CopyMatrix: different sizes!");
+            alglib.ap.assert(js2-js1==jd2-jd1, "CopyMatrix: different sizes!");
+            for(isrc=is1; isrc<=is2; isrc++)
+            {
+                idst = isrc-is1+id1;
+                i1_ = (js1) - (jd1);
+                for(i_=jd1; i_<=jd2;i_++)
+                {
+                    b[idst,i_] = a[isrc,i_+i1_];
+                }
+            }
+        }
+
+
+        public static void inplacetranspose(ref double[,] a,
+            int i1,
+            int i2,
+            int j1,
+            int j2,
+            ref double[] work)
+        {
+            int i = 0;
+            int j = 0;
+            int ips = 0;
+            int jps = 0;
+            int l = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            if( i1>i2 || j1>j2 )
+            {
+                return;
+            }
+            alglib.ap.assert(i1-i2==j1-j2, "InplaceTranspose error: incorrect array size!");
+            for(i=i1; i<=i2-1; i++)
+            {
+                j = j1+i-i1;
+                ips = i+1;
+                jps = j1+ips-i1;
+                l = i2-i;
+                i1_ = (ips) - (1);
+                for(i_=1; i_<=l;i_++)
+                {
+                    work[i_] = a[i_+i1_,j];
+                }
+                i1_ = (jps) - (ips);
+                for(i_=ips; i_<=i2;i_++)
+                {
+                    a[i_,j] = a[i,i_+i1_];
+                }
+                i1_ = (1) - (jps);
+                for(i_=jps; i_<=j2;i_++)
+                {
+                    a[i,i_] = work[i_+i1_];
+                }
+            }
+        }
+
+
+        public static void copyandtranspose(double[,] a,
+            int is1,
+            int is2,
+            int js1,
+            int js2,
+            ref double[,] b,
+            int id1,
+            int id2,
+            int jd1,
+            int jd2)
+        {
+            int isrc = 0;
+            int jdst = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            if( is1>is2 || js1>js2 )
+            {
+                return;
+            }
+            alglib.ap.assert(is2-is1==jd2-jd1, "CopyAndTranspose: different sizes!");
+            alglib.ap.assert(js2-js1==id2-id1, "CopyAndTranspose: different sizes!");
+            for(isrc=is1; isrc<=is2; isrc++)
+            {
+                jdst = isrc-is1+jd1;
+                i1_ = (js1) - (id1);
+                for(i_=id1; i_<=id2;i_++)
+                {
+                    b[i_,jdst] = a[isrc,i_+i1_];
+                }
+            }
+        }
+
+
+        public static void matrixvectormultiply(double[,] a,
+            int i1,
+            int i2,
+            int j1,
+            int j2,
+            bool trans,
+            double[] x,
+            int ix1,
+            int ix2,
+            double alpha,
+            ref double[] y,
+            int iy1,
+            int iy2,
+            double beta)
+        {
+            int i = 0;
+            double v = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            if( !trans )
+            {
+                
+                //
+                // y := alpha*A*x + beta*y;
+                //
+                if( i1>i2 || j1>j2 )
+                {
+                    return;
+                }
+                alglib.ap.assert(j2-j1==ix2-ix1, "MatrixVectorMultiply: A and X dont match!");
+                alglib.ap.assert(i2-i1==iy2-iy1, "MatrixVectorMultiply: A and Y dont match!");
+                
+                //
+                // beta*y
+                //
+                if( (double)(beta)==(double)(0) )
+                {
+                    for(i=iy1; i<=iy2; i++)
+                    {
+                        y[i] = 0;
+                    }
+                }
+                else
+                {
+                    for(i_=iy1; i_<=iy2;i_++)
+                    {
+                        y[i_] = beta*y[i_];
+                    }
+                }
+                
+                //
+                // alpha*A*x
+                //
+                for(i=i1; i<=i2; i++)
+                {
+                    i1_ = (ix1)-(j1);
+                    v = 0.0;
+                    for(i_=j1; i_<=j2;i_++)
+                    {
+                        v += a[i,i_]*x[i_+i1_];
+                    }
+                    y[iy1+i-i1] = y[iy1+i-i1]+alpha*v;
+                }
+            }
+            else
+            {
+                
+                //
+                // y := alpha*A'*x + beta*y;
+                //
+                if( i1>i2 || j1>j2 )
+                {
+                    return;
+                }
+                alglib.ap.assert(i2-i1==ix2-ix1, "MatrixVectorMultiply: A and X dont match!");
+                alglib.ap.assert(j2-j1==iy2-iy1, "MatrixVectorMultiply: A and Y dont match!");
+                
+                //
+                // beta*y
+                //
+                if( (double)(beta)==(double)(0) )
+                {
+                    for(i=iy1; i<=iy2; i++)
+                    {
+                        y[i] = 0;
+                    }
+                }
+                else
+                {
+                    for(i_=iy1; i_<=iy2;i_++)
+                    {
+                        y[i_] = beta*y[i_];
+                    }
+                }
+                
+                //
+                // alpha*A'*x
+                //
+                for(i=i1; i<=i2; i++)
+                {
+                    v = alpha*x[ix1+i-i1];
+                    i1_ = (j1) - (iy1);
+                    for(i_=iy1; i_<=iy2;i_++)
+                    {
+                        y[i_] = y[i_] + v*a[i,i_+i1_];
+                    }
+                }
+            }
+        }
+
+
+        public static double pythag2(double x,
+            double y)
+        {
+            double result = 0;
+            double w = 0;
+            double xabs = 0;
+            double yabs = 0;
+            double z = 0;
+
+            xabs = Math.Abs(x);
+            yabs = Math.Abs(y);
+            w = Math.Max(xabs, yabs);
+            z = Math.Min(xabs, yabs);
+            if( (double)(z)==(double)(0) )
+            {
+                result = w;
+            }
+            else
+            {
+                result = w*Math.Sqrt(1+math.sqr(z/w));
+            }
+            return result;
+        }
+
+
+        public static void matrixmatrixmultiply(double[,] a,
+            int ai1,
+            int ai2,
+            int aj1,
+            int aj2,
+            bool transa,
+            double[,] b,
+            int bi1,
+            int bi2,
+            int bj1,
+            int bj2,
+            bool transb,
+            double alpha,
+            ref double[,] c,
+            int ci1,
+            int ci2,
+            int cj1,
+            int cj2,
+            double beta,
+            ref double[] work)
+        {
+            int arows = 0;
+            int acols = 0;
+            int brows = 0;
+            int bcols = 0;
+            int crows = 0;
+            int ccols = 0;
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int l = 0;
+            int r = 0;
+            double v = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            
+            //
+            // Setup
+            //
+            if( !transa )
+            {
+                arows = ai2-ai1+1;
+                acols = aj2-aj1+1;
+            }
+            else
+            {
+                arows = aj2-aj1+1;
+                acols = ai2-ai1+1;
+            }
+            if( !transb )
+            {
+                brows = bi2-bi1+1;
+                bcols = bj2-bj1+1;
+            }
+            else
+            {
+                brows = bj2-bj1+1;
+                bcols = bi2-bi1+1;
+            }
+            alglib.ap.assert(acols==brows, "MatrixMatrixMultiply: incorrect matrix sizes!");
+            if( ((arows<=0 || acols<=0) || brows<=0) || bcols<=0 )
+            {
+                return;
+            }
+            crows = arows;
+            ccols = bcols;
+            
+            //
+            // Test WORK
+            //
+            i = Math.Max(arows, acols);
+            i = Math.Max(brows, i);
+            i = Math.Max(i, bcols);
+            work[1] = 0;
+            work[i] = 0;
+            
+            //
+            // Prepare C
+            //
+            if( (double)(beta)==(double)(0) )
+            {
+                for(i=ci1; i<=ci2; i++)
+                {
+                    for(j=cj1; j<=cj2; j++)
+                    {
+                        c[i,j] = 0;
+                    }
+                }
+            }
+            else
+            {
+                for(i=ci1; i<=ci2; i++)
+                {
+                    for(i_=cj1; i_<=cj2;i_++)
+                    {
+                        c[i,i_] = beta*c[i,i_];
+                    }
+                }
+            }
+            
+            //
+            // A*B
+            //
+            if( !transa && !transb )
+            {
+                for(l=ai1; l<=ai2; l++)
+                {
+                    for(r=bi1; r<=bi2; r++)
+                    {
+                        v = alpha*a[l,aj1+r-bi1];
+                        k = ci1+l-ai1;
+                        i1_ = (bj1) - (cj1);
+                        for(i_=cj1; i_<=cj2;i_++)
+                        {
+                            c[k,i_] = c[k,i_] + v*b[r,i_+i1_];
+                        }
+                    }
+                }
+                return;
+            }
+            
+            //
+            // A*B'
+            //
+            if( !transa && transb )
+            {
+                if( arows*acols<brows*bcols )
+                {
+                    for(r=bi1; r<=bi2; r++)
+                    {
+                        for(l=ai1; l<=ai2; l++)
+                        {
+                            i1_ = (bj1)-(aj1);
+                            v = 0.0;
+                            for(i_=aj1; i_<=aj2;i_++)
+                            {
+                                v += a[l,i_]*b[r,i_+i1_];
+                            }
+                            c[ci1+l-ai1,cj1+r-bi1] = c[ci1+l-ai1,cj1+r-bi1]+alpha*v;
+                        }
+                    }
+                    return;
+                }
+                else
+                {
+                    for(l=ai1; l<=ai2; l++)
+                    {
+                        for(r=bi1; r<=bi2; r++)
+                        {
+                            i1_ = (bj1)-(aj1);
+                            v = 0.0;
+                            for(i_=aj1; i_<=aj2;i_++)
+                            {
+                                v += a[l,i_]*b[r,i_+i1_];
+                            }
+                            c[ci1+l-ai1,cj1+r-bi1] = c[ci1+l-ai1,cj1+r-bi1]+alpha*v;
+                        }
+                    }
+                    return;
+                }
+            }
+            
+            //
+            // A'*B
+            //
+            if( transa && !transb )
+            {
+                for(l=aj1; l<=aj2; l++)
+                {
+                    for(r=bi1; r<=bi2; r++)
+                    {
+                        v = alpha*a[ai1+r-bi1,l];
+                        k = ci1+l-aj1;
+                        i1_ = (bj1) - (cj1);
+                        for(i_=cj1; i_<=cj2;i_++)
+                        {
+                            c[k,i_] = c[k,i_] + v*b[r,i_+i1_];
+                        }
+                    }
+                }
+                return;
+            }
+            
+            //
+            // A'*B'
+            //
+            if( transa && transb )
+            {
+                if( arows*acols<brows*bcols )
+                {
+                    for(r=bi1; r<=bi2; r++)
+                    {
+                        k = cj1+r-bi1;
+                        for(i=1; i<=crows; i++)
+                        {
+                            work[i] = 0.0;
+                        }
+                        for(l=ai1; l<=ai2; l++)
+                        {
+                            v = alpha*b[r,bj1+l-ai1];
+                            i1_ = (aj1) - (1);
+                            for(i_=1; i_<=crows;i_++)
+                            {
+                                work[i_] = work[i_] + v*a[l,i_+i1_];
+                            }
+                        }
+                        i1_ = (1) - (ci1);
+                        for(i_=ci1; i_<=ci2;i_++)
+                        {
+                            c[i_,k] = c[i_,k] + work[i_+i1_];
+                        }
+                    }
+                    return;
+                }
+                else
+                {
+                    for(l=aj1; l<=aj2; l++)
+                    {
+                        k = ai2-ai1+1;
+                        i1_ = (ai1) - (1);
+                        for(i_=1; i_<=k;i_++)
+                        {
+                            work[i_] = a[i_+i1_,l];
+                        }
+                        for(r=bi1; r<=bi2; r++)
+                        {
+                            i1_ = (bj1)-(1);
+                            v = 0.0;
+                            for(i_=1; i_<=k;i_++)
+                            {
+                                v += work[i_]*b[r,i_+i1_];
+                            }
+                            c[ci1+l-aj1,cj1+r-bi1] = c[ci1+l-aj1,cj1+r-bi1]+alpha*v;
+                        }
+                    }
+                    return;
+                }
+            }
+        }
+
+
+    }
+    public class rotations
+    {
+        /*************************************************************************
+        Application of a sequence of  elementary rotations to a matrix
+
+        The algorithm pre-multiplies the matrix by a sequence of rotation
+        transformations which is given by arrays C and S. Depending on the value
+        of the IsForward parameter either 1 and 2, 3 and 4 and so on (if IsForward=true)
+        rows are rotated, or the rows N and N-1, N-2 and N-3 and so on, are rotated.
+
+        Not the whole matrix but only a part of it is transformed (rows from M1 to
+        M2, columns from N1 to N2). Only the elements of this submatrix are changed.
+
+        Input parameters:
+            IsForward   -   the sequence of the rotation application.
+            M1,M2       -   the range of rows to be transformed.
+            N1, N2      -   the range of columns to be transformed.
+            C,S         -   transformation coefficients.
+                            Array whose index ranges within [1..M2-M1].
+            A           -   processed matrix.
+            WORK        -   working array whose index ranges within [N1..N2].
+
+        Output parameters:
+            A           -   transformed matrix.
+
+        Utility subroutine.
+        *************************************************************************/
+        public static void applyrotationsfromtheleft(bool isforward,
+            int m1,
+            int m2,
+            int n1,
+            int n2,
+            double[] c,
+            double[] s,
+            ref double[,] a,
+            ref double[] work)
+        {
+            int j = 0;
+            int jp1 = 0;
+            double ctemp = 0;
+            double stemp = 0;
+            double temp = 0;
+            int i_ = 0;
+
+            if( m1>m2 || n1>n2 )
+            {
+                return;
+            }
+            
+            //
+            // Form  P * A
+            //
+            if( isforward )
+            {
+                if( n1!=n2 )
+                {
+                    
+                    //
+                    // Common case: N1<>N2
+                    //
+                    for(j=m1; j<=m2-1; j++)
+                    {
+                        ctemp = c[j-m1+1];
+                        stemp = s[j-m1+1];
+                        if( (double)(ctemp)!=(double)(1) || (double)(stemp)!=(double)(0) )
+                        {
+                            jp1 = j+1;
+                            for(i_=n1; i_<=n2;i_++)
+                            {
+                                work[i_] = ctemp*a[jp1,i_];
+                            }
+                            for(i_=n1; i_<=n2;i_++)
+                            {
+                                work[i_] = work[i_] - stemp*a[j,i_];
+                            }
+                            for(i_=n1; i_<=n2;i_++)
+                            {
+                                a[j,i_] = ctemp*a[j,i_];
+                            }
+                            for(i_=n1; i_<=n2;i_++)
+                            {
+                                a[j,i_] = a[j,i_] + stemp*a[jp1,i_];
+                            }
+                            for(i_=n1; i_<=n2;i_++)
+                            {
+                                a[jp1,i_] = work[i_];
+                            }
+                        }
+                    }
+                }
+                else
+                {
+                    
+                    //
+                    // Special case: N1=N2
+                    //
+                    for(j=m1; j<=m2-1; j++)
+                    {
+                        ctemp = c[j-m1+1];
+                        stemp = s[j-m1+1];
+                        if( (double)(ctemp)!=(double)(1) || (double)(stemp)!=(double)(0) )
+                        {
+                            temp = a[j+1,n1];
+                            a[j+1,n1] = ctemp*temp-stemp*a[j,n1];
+                            a[j,n1] = stemp*temp+ctemp*a[j,n1];
+                        }
+                    }
+                }
+            }
+            else
+            {
+                if( n1!=n2 )
+                {
+                    
+                    //
+                    // Common case: N1<>N2
+                    //
+                    for(j=m2-1; j>=m1; j--)
+                    {
+                        ctemp = c[j-m1+1];
+                        stemp = s[j-m1+1];
+                        if( (double)(ctemp)!=(double)(1) || (double)(stemp)!=(double)(0) )
+                        {
+                            jp1 = j+1;
+                            for(i_=n1; i_<=n2;i_++)
+                            {
+                                work[i_] = ctemp*a[jp1,i_];
+                            }
+                            for(i_=n1; i_<=n2;i_++)
+                            {
+                                work[i_] = work[i_] - stemp*a[j,i_];
+                            }
+                            for(i_=n1; i_<=n2;i_++)
+                            {
+                                a[j,i_] = ctemp*a[j,i_];
+                            }
+                            for(i_=n1; i_<=n2;i_++)
+                            {
+                                a[j,i_] = a[j,i_] + stemp*a[jp1,i_];
+                            }
+                            for(i_=n1; i_<=n2;i_++)
+                            {
+                                a[jp1,i_] = work[i_];
+                            }
+                        }
+                    }
+                }
+                else
+                {
+                    
+                    //
+                    // Special case: N1=N2
+                    //
+                    for(j=m2-1; j>=m1; j--)
+                    {
+                        ctemp = c[j-m1+1];
+                        stemp = s[j-m1+1];
+                        if( (double)(ctemp)!=(double)(1) || (double)(stemp)!=(double)(0) )
+                        {
+                            temp = a[j+1,n1];
+                            a[j+1,n1] = ctemp*temp-stemp*a[j,n1];
+                            a[j,n1] = stemp*temp+ctemp*a[j,n1];
+                        }
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Application of a sequence of  elementary rotations to a matrix
+
+        The algorithm post-multiplies the matrix by a sequence of rotation
+        transformations which is given by arrays C and S. Depending on the value
+        of the IsForward parameter either 1 and 2, 3 and 4 and so on (if IsForward=true)
+        rows are rotated, or the rows N and N-1, N-2 and N-3 and so on are rotated.
+
+        Not the whole matrix but only a part of it is transformed (rows from M1
+        to M2, columns from N1 to N2). Only the elements of this submatrix are changed.
+
+        Input parameters:
+            IsForward   -   the sequence of the rotation application.
+            M1,M2       -   the range of rows to be transformed.
+            N1, N2      -   the range of columns to be transformed.
+            C,S         -   transformation coefficients.
+                            Array whose index ranges within [1..N2-N1].
+            A           -   processed matrix.
+            WORK        -   working array whose index ranges within [M1..M2].
+
+        Output parameters:
+            A           -   transformed matrix.
+
+        Utility subroutine.
+        *************************************************************************/
+        public static void applyrotationsfromtheright(bool isforward,
+            int m1,
+            int m2,
+            int n1,
+            int n2,
+            double[] c,
+            double[] s,
+            ref double[,] a,
+            ref double[] work)
+        {
+            int j = 0;
+            int jp1 = 0;
+            double ctemp = 0;
+            double stemp = 0;
+            double temp = 0;
+            int i_ = 0;
+
+            
+            //
+            // Form A * P'
+            //
+            if( isforward )
+            {
+                if( m1!=m2 )
+                {
+                    
+                    //
+                    // Common case: M1<>M2
+                    //
+                    for(j=n1; j<=n2-1; j++)
+                    {
+                        ctemp = c[j-n1+1];
+                        stemp = s[j-n1+1];
+                        if( (double)(ctemp)!=(double)(1) || (double)(stemp)!=(double)(0) )
+                        {
+                            jp1 = j+1;
+                            for(i_=m1; i_<=m2;i_++)
+                            {
+                                work[i_] = ctemp*a[i_,jp1];
+                            }
+                            for(i_=m1; i_<=m2;i_++)
+                            {
+                                work[i_] = work[i_] - stemp*a[i_,j];
+                            }
+                            for(i_=m1; i_<=m2;i_++)
+                            {
+                                a[i_,j] = ctemp*a[i_,j];
+                            }
+                            for(i_=m1; i_<=m2;i_++)
+                            {
+                                a[i_,j] = a[i_,j] + stemp*a[i_,jp1];
+                            }
+                            for(i_=m1; i_<=m2;i_++)
+                            {
+                                a[i_,jp1] = work[i_];
+                            }
+                        }
+                    }
+                }
+                else
+                {
+                    
+                    //
+                    // Special case: M1=M2
+                    //
+                    for(j=n1; j<=n2-1; j++)
+                    {
+                        ctemp = c[j-n1+1];
+                        stemp = s[j-n1+1];
+                        if( (double)(ctemp)!=(double)(1) || (double)(stemp)!=(double)(0) )
+                        {
+                            temp = a[m1,j+1];
+                            a[m1,j+1] = ctemp*temp-stemp*a[m1,j];
+                            a[m1,j] = stemp*temp+ctemp*a[m1,j];
+                        }
+                    }
+                }
+            }
+            else
+            {
+                if( m1!=m2 )
+                {
+                    
+                    //
+                    // Common case: M1<>M2
+                    //
+                    for(j=n2-1; j>=n1; j--)
+                    {
+                        ctemp = c[j-n1+1];
+                        stemp = s[j-n1+1];
+                        if( (double)(ctemp)!=(double)(1) || (double)(stemp)!=(double)(0) )
+                        {
+                            jp1 = j+1;
+                            for(i_=m1; i_<=m2;i_++)
+                            {
+                                work[i_] = ctemp*a[i_,jp1];
+                            }
+                            for(i_=m1; i_<=m2;i_++)
+                            {
+                                work[i_] = work[i_] - stemp*a[i_,j];
+                            }
+                            for(i_=m1; i_<=m2;i_++)
+                            {
+                                a[i_,j] = ctemp*a[i_,j];
+                            }
+                            for(i_=m1; i_<=m2;i_++)
+                            {
+                                a[i_,j] = a[i_,j] + stemp*a[i_,jp1];
+                            }
+                            for(i_=m1; i_<=m2;i_++)
+                            {
+                                a[i_,jp1] = work[i_];
+                            }
+                        }
+                    }
+                }
+                else
+                {
+                    
+                    //
+                    // Special case: M1=M2
+                    //
+                    for(j=n2-1; j>=n1; j--)
+                    {
+                        ctemp = c[j-n1+1];
+                        stemp = s[j-n1+1];
+                        if( (double)(ctemp)!=(double)(1) || (double)(stemp)!=(double)(0) )
+                        {
+                            temp = a[m1,j+1];
+                            a[m1,j+1] = ctemp*temp-stemp*a[m1,j];
+                            a[m1,j] = stemp*temp+ctemp*a[m1,j];
+                        }
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        The subroutine generates the elementary rotation, so that:
+
+        [  CS  SN  ]  .  [ F ]  =  [ R ]
+        [ -SN  CS  ]     [ G ]     [ 0 ]
+
+        CS**2 + SN**2 = 1
+        *************************************************************************/
+        public static void generaterotation(double f,
+            double g,
+            ref double cs,
+            ref double sn,
+            ref double r)
+        {
+            double f1 = 0;
+            double g1 = 0;
+
+            cs = 0;
+            sn = 0;
+            r = 0;
+
+            if( (double)(g)==(double)(0) )
+            {
+                cs = 1;
+                sn = 0;
+                r = f;
+            }
+            else
+            {
+                if( (double)(f)==(double)(0) )
+                {
+                    cs = 0;
+                    sn = 1;
+                    r = g;
+                }
+                else
+                {
+                    f1 = f;
+                    g1 = g;
+                    if( (double)(Math.Abs(f1))>(double)(Math.Abs(g1)) )
+                    {
+                        r = Math.Abs(f1)*Math.Sqrt(1+math.sqr(g1/f1));
+                    }
+                    else
+                    {
+                        r = Math.Abs(g1)*Math.Sqrt(1+math.sqr(f1/g1));
+                    }
+                    cs = f1/r;
+                    sn = g1/r;
+                    if( (double)(Math.Abs(f))>(double)(Math.Abs(g)) && (double)(cs)<(double)(0) )
+                    {
+                        cs = -cs;
+                        sn = -sn;
+                        r = -r;
+                    }
+                }
+            }
+        }
+
+
+    }
+    public class hsschur
+    {
+        /*************************************************************************
+        Subroutine performing  the  Schur  decomposition  of  a  matrix  in  upper
+        Hessenberg form using the QR algorithm with multiple shifts.
+
+        The  source matrix  H  is  represented as  S'*H*S = T, where H - matrix in
+        upper Hessenberg form,  S - orthogonal matrix (Schur vectors),   T - upper
+        quasi-triangular matrix (with blocks of sizes  1x1  and  2x2  on  the main
+        diagonal).
+
+        Input parameters:
+            H   -   matrix to be decomposed.
+                    Array whose indexes range within [1..N, 1..N].
+            N   -   size of H, N>=0.
+
+
+        Output parameters:
+            H      contains the matrix T.
+                    Array whose indexes range within [1..N, 1..N].
+                    All elements below the blocks on the main diagonal are equal
+                    to 0.
+            S   -   contains Schur vectors.
+                    Array whose indexes range within [1..N, 1..N].
+
+        Note 1:
+            The block structure of matrix T could be easily recognized: since  all
+            the elements  below  the blocks are zeros, the elements a[i+1,i] which
+            are equal to 0 show the block border.
+
+        Note 2:
+            the algorithm  performance  depends  on  the  value  of  the  internal
+            parameter NS of InternalSchurDecomposition  subroutine  which  defines
+            the number of shifts in the QR algorithm (analog of  the  block  width
+            in block matrix algorithms in linear algebra). If you require  maximum
+            performance  on  your  machine,  it  is  recommended  to  adjust  this
+            parameter manually.
+
+        Result:
+            True, if the algorithm has converged and the parameters H and S contain
+                the result.
+            False, if the algorithm has not converged.
+
+        Algorithm implemented on the basis of subroutine DHSEQR (LAPACK 3.0 library).
+        *************************************************************************/
+        public static bool upperhessenbergschurdecomposition(ref double[,] h,
+            int n,
+            ref double[,] s)
+        {
+            bool result = new bool();
+            double[] wi = new double[0];
+            double[] wr = new double[0];
+            int info = 0;
+
+            s = new double[0,0];
+
+            internalschurdecomposition(ref h, n, 1, 2, ref wr, ref wi, ref s, ref info);
+            result = info==0;
+            return result;
+        }
+
+
+        public static void internalschurdecomposition(ref double[,] h,
+            int n,
+            int tneeded,
+            int zneeded,
+            ref double[] wr,
+            ref double[] wi,
+            ref double[,] z,
+            ref int info)
+        {
+            double[] work = new double[0];
+            int i = 0;
+            int i1 = 0;
+            int i2 = 0;
+            int ierr = 0;
+            int ii = 0;
+            int itemp = 0;
+            int itn = 0;
+            int its = 0;
+            int j = 0;
+            int k = 0;
+            int l = 0;
+            int maxb = 0;
+            int nr = 0;
+            int ns = 0;
+            int nv = 0;
+            double absw = 0;
+            double ovfl = 0;
+            double smlnum = 0;
+            double tau = 0;
+            double temp = 0;
+            double tst1 = 0;
+            double ulp = 0;
+            double unfl = 0;
+            double[,] s = new double[0,0];
+            double[] v = new double[0];
+            double[] vv = new double[0];
+            double[] workc1 = new double[0];
+            double[] works1 = new double[0];
+            double[] workv3 = new double[0];
+            double[] tmpwr = new double[0];
+            double[] tmpwi = new double[0];
+            bool initz = new bool();
+            bool wantt = new bool();
+            bool wantz = new bool();
+            double cnst = 0;
+            bool failflag = new bool();
+            int p1 = 0;
+            int p2 = 0;
+            double vt = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            wr = new double[0];
+            wi = new double[0];
+            info = 0;
+
+            
+            //
+            // Set the order of the multi-shift QR algorithm to be used.
+            // If you want to tune algorithm, change this values
+            //
+            ns = 12;
+            maxb = 50;
+            
+            //
+            // Now 2 < NS <= MAXB < NH.
+            //
+            maxb = Math.Max(3, maxb);
+            ns = Math.Min(maxb, ns);
+            
+            //
+            // Initialize
+            //
+            cnst = 1.5;
+            work = new double[Math.Max(n, 1)+1];
+            s = new double[ns+1, ns+1];
+            v = new double[ns+1+1];
+            vv = new double[ns+1+1];
+            wr = new double[Math.Max(n, 1)+1];
+            wi = new double[Math.Max(n, 1)+1];
+            workc1 = new double[1+1];
+            works1 = new double[1+1];
+            workv3 = new double[3+1];
+            tmpwr = new double[Math.Max(n, 1)+1];
+            tmpwi = new double[Math.Max(n, 1)+1];
+            alglib.ap.assert(n>=0, "InternalSchurDecomposition: incorrect N!");
+            alglib.ap.assert(tneeded==0 || tneeded==1, "InternalSchurDecomposition: incorrect TNeeded!");
+            alglib.ap.assert((zneeded==0 || zneeded==1) || zneeded==2, "InternalSchurDecomposition: incorrect ZNeeded!");
+            wantt = tneeded==1;
+            initz = zneeded==2;
+            wantz = zneeded!=0;
+            info = 0;
+            
+            //
+            // Initialize Z, if necessary
+            //
+            if( initz )
+            {
+                z = new double[n+1, n+1];
+                for(i=1; i<=n; i++)
+                {
+                    for(j=1; j<=n; j++)
+                    {
+                        if( i==j )
+                        {
+                            z[i,j] = 1;
+                        }
+                        else
+                        {
+                            z[i,j] = 0;
+                        }
+                    }
+                }
+            }
+            
+            //
+            // Quick return if possible
+            //
+            if( n==0 )
+            {
+                return;
+            }
+            if( n==1 )
+            {
+                wr[1] = h[1,1];
+                wi[1] = 0;
+                return;
+            }
+            
+            //
+            // Set rows and columns 1 to N to zero below the first
+            // subdiagonal.
+            //
+            for(j=1; j<=n-2; j++)
+            {
+                for(i=j+2; i<=n; i++)
+                {
+                    h[i,j] = 0;
+                }
+            }
+            
+            //
+            // Test if N is sufficiently small
+            //
+            if( (ns<=2 || ns>n) || maxb>=n )
+            {
+                
+                //
+                // Use the standard double-shift algorithm
+                //
+                internalauxschur(wantt, wantz, n, 1, n, ref h, ref wr, ref wi, 1, n, ref z, ref work, ref workv3, ref workc1, ref works1, ref info);
+                
+                //
+                // fill entries under diagonal blocks of T with zeros
+                //
+                if( wantt )
+                {
+                    j = 1;
+                    while( j<=n )
+                    {
+                        if( (double)(wi[j])==(double)(0) )
+                        {
+                            for(i=j+1; i<=n; i++)
+                            {
+                                h[i,j] = 0;
+                            }
+                            j = j+1;
+                        }
+                        else
+                        {
+                            for(i=j+2; i<=n; i++)
+                            {
+                                h[i,j] = 0;
+                                h[i,j+1] = 0;
+                            }
+                            j = j+2;
+                        }
+                    }
+                }
+                return;
+            }
+            unfl = math.minrealnumber;
+            ovfl = 1/unfl;
+            ulp = 2*math.machineepsilon;
+            smlnum = unfl*(n/ulp);
+            
+            //
+            // I1 and I2 are the indices of the first row and last column of H
+            // to which transformations must be applied. If eigenvalues only are
+            // being computed, I1 and I2 are set inside the main loop.
+            //
+            i1 = 1;
+            i2 = n;
+            
+            //
+            // ITN is the total number of multiple-shift QR iterations allowed.
+            //
+            itn = 30*n;
+            
+            //
+            // The main loop begins here. I is the loop index and decreases from
+            // IHI to ILO in steps of at most MAXB. Each iteration of the loop
+            // works with the active submatrix in rows and columns L to I.
+            // Eigenvalues I+1 to IHI have already converged. Either L = ILO or
+            // H(L,L-1) is negligible so that the matrix splits.
+            //
+            i = n;
+            while( true )
+            {
+                l = 1;
+                if( i<1 )
+                {
+                    
+                    //
+                    // fill entries under diagonal blocks of T with zeros
+                    //
+                    if( wantt )
+                    {
+                        j = 1;
+                        while( j<=n )
+                        {
+                            if( (double)(wi[j])==(double)(0) )
+                            {
+                                for(i=j+1; i<=n; i++)
+                                {
+                                    h[i,j] = 0;
+                                }
+                                j = j+1;
+                            }
+                            else
+                            {
+                                for(i=j+2; i<=n; i++)
+                                {
+                                    h[i,j] = 0;
+                                    h[i,j+1] = 0;
+                                }
+                                j = j+2;
+                            }
+                        }
+                    }
+                    
+                    //
+                    // Exit
+                    //
+                    return;
+                }
+                
+                //
+                // Perform multiple-shift QR iterations on rows and columns ILO to I
+                // until a submatrix of order at most MAXB splits off at the bottom
+                // because a subdiagonal element has become negligible.
+                //
+                failflag = true;
+                for(its=0; its<=itn; its++)
+                {
+                    
+                    //
+                    // Look for a single small subdiagonal element.
+                    //
+                    for(k=i; k>=l+1; k--)
+                    {
+                        tst1 = Math.Abs(h[k-1,k-1])+Math.Abs(h[k,k]);
+                        if( (double)(tst1)==(double)(0) )
+                        {
+                            tst1 = blas.upperhessenberg1norm(h, l, i, l, i, ref work);
+                        }
+                        if( (double)(Math.Abs(h[k,k-1]))<=(double)(Math.Max(ulp*tst1, smlnum)) )
+                        {
+                            break;
+                        }
+                    }
+                    l = k;
+                    if( l>1 )
+                    {
+                        
+                        //
+                        // H(L,L-1) is negligible.
+                        //
+                        h[l,l-1] = 0;
+                    }
+                    
+                    //
+                    // Exit from loop if a submatrix of order <= MAXB has split off.
+                    //
+                    if( l>=i-maxb+1 )
+                    {
+                        failflag = false;
+                        break;
+                    }
+                    
+                    //
+                    // Now the active submatrix is in rows and columns L to I. If
+                    // eigenvalues only are being computed, only the active submatrix
+                    // need be transformed.
+                    //
+                    if( its==20 || its==30 )
+                    {
+                        
+                        //
+                        // Exceptional shifts.
+                        //
+                        for(ii=i-ns+1; ii<=i; ii++)
+                        {
+                            wr[ii] = cnst*(Math.Abs(h[ii,ii-1])+Math.Abs(h[ii,ii]));
+                            wi[ii] = 0;
+                        }
+                    }
+                    else
+                    {
+                        
+                        //
+                        // Use eigenvalues of trailing submatrix of order NS as shifts.
+                        //
+                        blas.copymatrix(h, i-ns+1, i, i-ns+1, i, ref s, 1, ns, 1, ns);
+                        internalauxschur(false, false, ns, 1, ns, ref s, ref tmpwr, ref tmpwi, 1, ns, ref z, ref work, ref workv3, ref workc1, ref works1, ref ierr);
+                        for(p1=1; p1<=ns; p1++)
+                        {
+                            wr[i-ns+p1] = tmpwr[p1];
+                            wi[i-ns+p1] = tmpwi[p1];
+                        }
+                        if( ierr>0 )
+                        {
+                            
+                            //
+                            // If DLAHQR failed to compute all NS eigenvalues, use the
+                            // unconverged diagonal elements as the remaining shifts.
+                            //
+                            for(ii=1; ii<=ierr; ii++)
+                            {
+                                wr[i-ns+ii] = s[ii,ii];
+                                wi[i-ns+ii] = 0;
+                            }
+                        }
+                    }
+                    
+                    //
+                    // Form the first column of (G-w(1)) (G-w(2)) . . . (G-w(ns))
+                    // where G is the Hessenberg submatrix H(L:I,L:I) and w is
+                    // the vector of shifts (stored in WR and WI). The result is
+                    // stored in the local array V.
+                    //
+                    v[1] = 1;
+                    for(ii=2; ii<=ns+1; ii++)
+                    {
+                        v[ii] = 0;
+                    }
+                    nv = 1;
+                    for(j=i-ns+1; j<=i; j++)
+                    {
+                        if( (double)(wi[j])>=(double)(0) )
+                        {
+                            if( (double)(wi[j])==(double)(0) )
+                            {
+                                
+                                //
+                                // real shift
+                                //
+                                p1 = nv+1;
+                                for(i_=1; i_<=p1;i_++)
+                                {
+                                    vv[i_] = v[i_];
+                                }
+                                blas.matrixvectormultiply(h, l, l+nv, l, l+nv-1, false, vv, 1, nv, 1.0, ref v, 1, nv+1, -wr[j]);
+                                nv = nv+1;
+                            }
+                            else
+                            {
+                                if( (double)(wi[j])>(double)(0) )
+                                {
+                                    
+                                    //
+                                    // complex conjugate pair of shifts
+                                    //
+                                    p1 = nv+1;
+                                    for(i_=1; i_<=p1;i_++)
+                                    {
+                                        vv[i_] = v[i_];
+                                    }
+                                    blas.matrixvectormultiply(h, l, l+nv, l, l+nv-1, false, v, 1, nv, 1.0, ref vv, 1, nv+1, -(2*wr[j]));
+                                    itemp = blas.vectoridxabsmax(vv, 1, nv+1);
+                                    temp = 1/Math.Max(Math.Abs(vv[itemp]), smlnum);
+                                    p1 = nv+1;
+                                    for(i_=1; i_<=p1;i_++)
+                                    {
+                                        vv[i_] = temp*vv[i_];
+                                    }
+                                    absw = blas.pythag2(wr[j], wi[j]);
+                                    temp = temp*absw*absw;
+                                    blas.matrixvectormultiply(h, l, l+nv+1, l, l+nv, false, vv, 1, nv+1, 1.0, ref v, 1, nv+2, temp);
+                                    nv = nv+2;
+                                }
+                            }
+                            
+                            //
+                            // Scale V(1:NV) so that max(abs(V(i))) = 1. If V is zero,
+                            // reset it to the unit vector.
+                            //
+                            itemp = blas.vectoridxabsmax(v, 1, nv);
+                            temp = Math.Abs(v[itemp]);
+                            if( (double)(temp)==(double)(0) )
+                            {
+                                v[1] = 1;
+                                for(ii=2; ii<=nv; ii++)
+                                {
+                                    v[ii] = 0;
+                                }
+                            }
+                            else
+                            {
+                                temp = Math.Max(temp, smlnum);
+                                vt = 1/temp;
+                                for(i_=1; i_<=nv;i_++)
+                                {
+                                    v[i_] = vt*v[i_];
+                                }
+                            }
+                        }
+                    }
+                    
+                    //
+                    // Multiple-shift QR step
+                    //
+                    for(k=l; k<=i-1; k++)
+                    {
+                        
+                        //
+                        // The first iteration of this loop determines a reflection G
+                        // from the vector V and applies it from left and right to H,
+                        // thus creating a nonzero bulge below the subdiagonal.
+                        //
+                        // Each subsequent iteration determines a reflection G to
+                        // restore the Hessenberg form in the (K-1)th column, and thus
+                        // chases the bulge one step toward the bottom of the active
+                        // submatrix. NR is the order of G.
+                        //
+                        nr = Math.Min(ns+1, i-k+1);
+                        if( k>l )
+                        {
+                            p1 = k-1;
+                            p2 = k+nr-1;
+                            i1_ = (k) - (1);
+                            for(i_=1; i_<=nr;i_++)
+                            {
+                                v[i_] = h[i_+i1_,p1];
+                            }
+                        }
+                        reflections.generatereflection(ref v, nr, ref tau);
+                        if( k>l )
+                        {
+                            h[k,k-1] = v[1];
+                            for(ii=k+1; ii<=i; ii++)
+                            {
+                                h[ii,k-1] = 0;
+                            }
+                        }
+                        v[1] = 1;
+                        
+                        //
+                        // Apply G from the left to transform the rows of the matrix in
+                        // columns K to I2.
+                        //
+                        reflections.applyreflectionfromtheleft(ref h, tau, v, k, k+nr-1, k, i2, ref work);
+                        
+                        //
+                        // Apply G from the right to transform the columns of the
+                        // matrix in rows I1 to min(K+NR,I).
+                        //
+                        reflections.applyreflectionfromtheright(ref h, tau, v, i1, Math.Min(k+nr, i), k, k+nr-1, ref work);
+                        if( wantz )
+                        {
+                            
+                            //
+                            // Accumulate transformations in the matrix Z
+                            //
+                            reflections.applyreflectionfromtheright(ref z, tau, v, 1, n, k, k+nr-1, ref work);
+                        }
+                    }
+                }
+                
+                //
+                // Failure to converge in remaining number of iterations
+                //
+                if( failflag )
+                {
+                    info = i;
+                    return;
+                }
+                
+                //
+                // A submatrix of order <= MAXB in rows and columns L to I has split
+                // off. Use the double-shift QR algorithm to handle it.
+                //
+                internalauxschur(wantt, wantz, n, l, i, ref h, ref wr, ref wi, 1, n, ref z, ref work, ref workv3, ref workc1, ref works1, ref info);
+                if( info>0 )
+                {
+                    return;
+                }
+                
+                //
+                // Decrement number of remaining iterations, and return to start of
+                // the main loop with a new value of I.
+                //
+                itn = itn-its;
+                i = l-1;
+            }
+        }
+
+
+        private static void internalauxschur(bool wantt,
+            bool wantz,
+            int n,
+            int ilo,
+            int ihi,
+            ref double[,] h,
+            ref double[] wr,
+            ref double[] wi,
+            int iloz,
+            int ihiz,
+            ref double[,] z,
+            ref double[] work,
+            ref double[] workv3,
+            ref double[] workc1,
+            ref double[] works1,
+            ref int info)
+        {
+            int i = 0;
+            int i1 = 0;
+            int i2 = 0;
+            int itn = 0;
+            int its = 0;
+            int j = 0;
+            int k = 0;
+            int l = 0;
+            int m = 0;
+            int nh = 0;
+            int nr = 0;
+            int nz = 0;
+            double ave = 0;
+            double cs = 0;
+            double disc = 0;
+            double h00 = 0;
+            double h10 = 0;
+            double h11 = 0;
+            double h12 = 0;
+            double h21 = 0;
+            double h22 = 0;
+            double h33 = 0;
+            double h33s = 0;
+            double h43h34 = 0;
+            double h44 = 0;
+            double h44s = 0;
+            double ovfl = 0;
+            double s = 0;
+            double smlnum = 0;
+            double sn = 0;
+            double sum = 0;
+            double t1 = 0;
+            double t2 = 0;
+            double t3 = 0;
+            double tst1 = 0;
+            double unfl = 0;
+            double v1 = 0;
+            double v2 = 0;
+            double v3 = 0;
+            bool failflag = new bool();
+            double dat1 = 0;
+            double dat2 = 0;
+            int p1 = 0;
+            double him1im1 = 0;
+            double him1i = 0;
+            double hiim1 = 0;
+            double hii = 0;
+            double wrim1 = 0;
+            double wri = 0;
+            double wiim1 = 0;
+            double wii = 0;
+            double ulp = 0;
+
+            info = 0;
+
+            info = 0;
+            dat1 = 0.75;
+            dat2 = -0.4375;
+            ulp = math.machineepsilon;
+            
+            //
+            // Quick return if possible
+            //
+            if( n==0 )
+            {
+                return;
+            }
+            if( ilo==ihi )
+            {
+                wr[ilo] = h[ilo,ilo];
+                wi[ilo] = 0;
+                return;
+            }
+            nh = ihi-ilo+1;
+            nz = ihiz-iloz+1;
+            
+            //
+            // Set machine-dependent constants for the stopping criterion.
+            // If norm(H) <= sqrt(OVFL), overflow should not occur.
+            //
+            unfl = math.minrealnumber;
+            ovfl = 1/unfl;
+            smlnum = unfl*(nh/ulp);
+            
+            //
+            // I1 and I2 are the indices of the first row and last column of H
+            // to which transformations must be applied. If eigenvalues only are
+            // being computed, I1 and I2 are set inside the main loop.
+            //
+            i1 = 1;
+            i2 = n;
+            
+            //
+            // ITN is the total number of QR iterations allowed.
+            //
+            itn = 30*nh;
+            
+            //
+            // The main loop begins here. I is the loop index and decreases from
+            // IHI to ILO in steps of 1 or 2. Each iteration of the loop works
+            // with the active submatrix in rows and columns L to I.
+            // Eigenvalues I+1 to IHI have already converged. Either L = ILO or
+            // H(L,L-1) is negligible so that the matrix splits.
+            //
+            i = ihi;
+            while( true )
+            {
+                l = ilo;
+                if( i<ilo )
+                {
+                    return;
+                }
+                
+                //
+                // Perform QR iterations on rows and columns ILO to I until a
+                // submatrix of order 1 or 2 splits off at the bottom because a
+                // subdiagonal element has become negligible.
+                //
+                failflag = true;
+                for(its=0; its<=itn; its++)
+                {
+                    
+                    //
+                    // Look for a single small subdiagonal element.
+                    //
+                    for(k=i; k>=l+1; k--)
+                    {
+                        tst1 = Math.Abs(h[k-1,k-1])+Math.Abs(h[k,k]);
+                        if( (double)(tst1)==(double)(0) )
+                        {
+                            tst1 = blas.upperhessenberg1norm(h, l, i, l, i, ref work);
+                        }
+                        if( (double)(Math.Abs(h[k,k-1]))<=(double)(Math.Max(ulp*tst1, smlnum)) )
+                        {
+                            break;
+                        }
+                    }
+                    l = k;
+                    if( l>ilo )
+                    {
+                        
+                        //
+                        // H(L,L-1) is negligible
+                        //
+                        h[l,l-1] = 0;
+                    }
+                    
+                    //
+                    // Exit from loop if a submatrix of order 1 or 2 has split off.
+                    //
+                    if( l>=i-1 )
+                    {
+                        failflag = false;
+                        break;
+                    }
+                    
+                    //
+                    // Now the active submatrix is in rows and columns L to I. If
+                    // eigenvalues only are being computed, only the active submatrix
+                    // need be transformed.
+                    //
+                    if( its==10 || its==20 )
+                    {
+                        
+                        //
+                        // Exceptional shift.
+                        //
+                        s = Math.Abs(h[i,i-1])+Math.Abs(h[i-1,i-2]);
+                        h44 = dat1*s+h[i,i];
+                        h33 = h44;
+                        h43h34 = dat2*s*s;
+                    }
+                    else
+                    {
+                        
+                        //
+                        // Prepare to use Francis' double shift
+                        // (i.e. 2nd degree generalized Rayleigh quotient)
+                        //
+                        h44 = h[i,i];
+                        h33 = h[i-1,i-1];
+                        h43h34 = h[i,i-1]*h[i-1,i];
+                        s = h[i-1,i-2]*h[i-1,i-2];
+                        disc = (h33-h44)*0.5;
+                        disc = disc*disc+h43h34;
+                        if( (double)(disc)>(double)(0) )
+                        {
+                            
+                            //
+                            // Real roots: use Wilkinson's shift twice
+                            //
+                            disc = Math.Sqrt(disc);
+                            ave = 0.5*(h33+h44);
+                            if( (double)(Math.Abs(h33)-Math.Abs(h44))>(double)(0) )
+                            {
+                                h33 = h33*h44-h43h34;
+                                h44 = h33/(extschursign(disc, ave)+ave);
+                            }
+                            else
+                            {
+                                h44 = extschursign(disc, ave)+ave;
+                            }
+                            h33 = h44;
+                            h43h34 = 0;
+                        }
+                    }
+                    
+                    //
+                    // Look for two consecutive small subdiagonal elements.
+                    //
+                    for(m=i-2; m>=l; m--)
+                    {
+                        
+                        //
+                        // Determine the effect of starting the double-shift QR
+                        // iteration at row M, and see if this would make H(M,M-1)
+                        // negligible.
+                        //
+                        h11 = h[m,m];
+                        h22 = h[m+1,m+1];
+                        h21 = h[m+1,m];
+                        h12 = h[m,m+1];
+                        h44s = h44-h11;
+                        h33s = h33-h11;
+                        v1 = (h33s*h44s-h43h34)/h21+h12;
+                        v2 = h22-h11-h33s-h44s;
+                        v3 = h[m+2,m+1];
+                        s = Math.Abs(v1)+Math.Abs(v2)+Math.Abs(v3);
+                        v1 = v1/s;
+                        v2 = v2/s;
+                        v3 = v3/s;
+                        workv3[1] = v1;
+                        workv3[2] = v2;
+                        workv3[3] = v3;
+                        if( m==l )
+                        {
+                            break;
+                        }
+                        h00 = h[m-1,m-1];
+                        h10 = h[m,m-1];
+                        tst1 = Math.Abs(v1)*(Math.Abs(h00)+Math.Abs(h11)+Math.Abs(h22));
+                        if( (double)(Math.Abs(h10)*(Math.Abs(v2)+Math.Abs(v3)))<=(double)(ulp*tst1) )
+                        {
+                            break;
+                        }
+                    }
+                    
+                    //
+                    // Double-shift QR step
+                    //
+                    for(k=m; k<=i-1; k++)
+                    {
+                        
+                        //
+                        // The first iteration of this loop determines a reflection G
+                        // from the vector V and applies it from left and right to H,
+                        // thus creating a nonzero bulge below the subdiagonal.
+                        //
+                        // Each subsequent iteration determines a reflection G to
+                        // restore the Hessenberg form in the (K-1)th column, and thus
+                        // chases the bulge one step toward the bottom of the active
+                        // submatrix. NR is the order of G.
+                        //
+                        nr = Math.Min(3, i-k+1);
+                        if( k>m )
+                        {
+                            for(p1=1; p1<=nr; p1++)
+                            {
+                                workv3[p1] = h[k+p1-1,k-1];
+                            }
+                        }
+                        reflections.generatereflection(ref workv3, nr, ref t1);
+                        if( k>m )
+                        {
+                            h[k,k-1] = workv3[1];
+                            h[k+1,k-1] = 0;
+                            if( k<i-1 )
+                            {
+                                h[k+2,k-1] = 0;
+                            }
+                        }
+                        else
+                        {
+                            if( m>l )
+                            {
+                                h[k,k-1] = -h[k,k-1];
+                            }
+                        }
+                        v2 = workv3[2];
+                        t2 = t1*v2;
+                        if( nr==3 )
+                        {
+                            v3 = workv3[3];
+                            t3 = t1*v3;
+                            
+                            //
+                            // Apply G from the left to transform the rows of the matrix
+                            // in columns K to I2.
+                            //
+                            for(j=k; j<=i2; j++)
+                            {
+                                sum = h[k,j]+v2*h[k+1,j]+v3*h[k+2,j];
+                                h[k,j] = h[k,j]-sum*t1;
+                                h[k+1,j] = h[k+1,j]-sum*t2;
+                                h[k+2,j] = h[k+2,j]-sum*t3;
+                            }
+                            
+                            //
+                            // Apply G from the right to transform the columns of the
+                            // matrix in rows I1 to min(K+3,I).
+                            //
+                            for(j=i1; j<=Math.Min(k+3, i); j++)
+                            {
+                                sum = h[j,k]+v2*h[j,k+1]+v3*h[j,k+2];
+                                h[j,k] = h[j,k]-sum*t1;
+                                h[j,k+1] = h[j,k+1]-sum*t2;
+                                h[j,k+2] = h[j,k+2]-sum*t3;
+                            }
+                            if( wantz )
+                            {
+                                
+                                //
+                                // Accumulate transformations in the matrix Z
+                                //
+                                for(j=iloz; j<=ihiz; j++)
+                                {
+                                    sum = z[j,k]+v2*z[j,k+1]+v3*z[j,k+2];
+                                    z[j,k] = z[j,k]-sum*t1;
+                                    z[j,k+1] = z[j,k+1]-sum*t2;
+                                    z[j,k+2] = z[j,k+2]-sum*t3;
+                                }
+                            }
+                        }
+                        else
+                        {
+                            if( nr==2 )
+                            {
+                                
+                                //
+                                // Apply G from the left to transform the rows of the matrix
+                                // in columns K to I2.
+                                //
+                                for(j=k; j<=i2; j++)
+                                {
+                                    sum = h[k,j]+v2*h[k+1,j];
+                                    h[k,j] = h[k,j]-sum*t1;
+                                    h[k+1,j] = h[k+1,j]-sum*t2;
+                                }
+                                
+                                //
+                                // Apply G from the right to transform the columns of the
+                                // matrix in rows I1 to min(K+3,I).
+                                //
+                                for(j=i1; j<=i; j++)
+                                {
+                                    sum = h[j,k]+v2*h[j,k+1];
+                                    h[j,k] = h[j,k]-sum*t1;
+                                    h[j,k+1] = h[j,k+1]-sum*t2;
+                                }
+                                if( wantz )
+                                {
+                                    
+                                    //
+                                    // Accumulate transformations in the matrix Z
+                                    //
+                                    for(j=iloz; j<=ihiz; j++)
+                                    {
+                                        sum = z[j,k]+v2*z[j,k+1];
+                                        z[j,k] = z[j,k]-sum*t1;
+                                        z[j,k+1] = z[j,k+1]-sum*t2;
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+                if( failflag )
+                {
+                    
+                    //
+                    // Failure to converge in remaining number of iterations
+                    //
+                    info = i;
+                    return;
+                }
+                if( l==i )
+                {
+                    
+                    //
+                    // H(I,I-1) is negligible: one eigenvalue has converged.
+                    //
+                    wr[i] = h[i,i];
+                    wi[i] = 0;
+                }
+                else
+                {
+                    if( l==i-1 )
+                    {
+                        
+                        //
+                        // H(I-1,I-2) is negligible: a pair of eigenvalues have converged.
+                        //
+                        //        Transform the 2-by-2 submatrix to standard Schur form,
+                        //        and compute and store the eigenvalues.
+                        //
+                        him1im1 = h[i-1,i-1];
+                        him1i = h[i-1,i];
+                        hiim1 = h[i,i-1];
+                        hii = h[i,i];
+                        aux2x2schur(ref him1im1, ref him1i, ref hiim1, ref hii, ref wrim1, ref wiim1, ref wri, ref wii, ref cs, ref sn);
+                        wr[i-1] = wrim1;
+                        wi[i-1] = wiim1;
+                        wr[i] = wri;
+                        wi[i] = wii;
+                        h[i-1,i-1] = him1im1;
+                        h[i-1,i] = him1i;
+                        h[i,i-1] = hiim1;
+                        h[i,i] = hii;
+                        if( wantt )
+                        {
+                            
+                            //
+                            // Apply the transformation to the rest of H.
+                            //
+                            if( i2>i )
+                            {
+                                workc1[1] = cs;
+                                works1[1] = sn;
+                                rotations.applyrotationsfromtheleft(true, i-1, i, i+1, i2, workc1, works1, ref h, ref work);
+                            }
+                            workc1[1] = cs;
+                            works1[1] = sn;
+                            rotations.applyrotationsfromtheright(true, i1, i-2, i-1, i, workc1, works1, ref h, ref work);
+                        }
+                        if( wantz )
+                        {
+                            
+                            //
+                            // Apply the transformation to Z.
+                            //
+                            workc1[1] = cs;
+                            works1[1] = sn;
+                            rotations.applyrotationsfromtheright(true, iloz, iloz+nz-1, i-1, i, workc1, works1, ref z, ref work);
+                        }
+                    }
+                }
+                
+                //
+                // Decrement number of remaining iterations, and return to start of
+                // the main loop with new value of I.
+                //
+                itn = itn-its;
+                i = l-1;
+            }
+        }
+
+
+        private static void aux2x2schur(ref double a,
+            ref double b,
+            ref double c,
+            ref double d,
+            ref double rt1r,
+            ref double rt1i,
+            ref double rt2r,
+            ref double rt2i,
+            ref double cs,
+            ref double sn)
+        {
+            double multpl = 0;
+            double aa = 0;
+            double bb = 0;
+            double bcmax = 0;
+            double bcmis = 0;
+            double cc = 0;
+            double cs1 = 0;
+            double dd = 0;
+            double eps = 0;
+            double p = 0;
+            double sab = 0;
+            double sac = 0;
+            double scl = 0;
+            double sigma = 0;
+            double sn1 = 0;
+            double tau = 0;
+            double temp = 0;
+            double z = 0;
+
+            rt1r = 0;
+            rt1i = 0;
+            rt2r = 0;
+            rt2i = 0;
+            cs = 0;
+            sn = 0;
+
+            multpl = 4.0;
+            eps = math.machineepsilon;
+            if( (double)(c)==(double)(0) )
+            {
+                cs = 1;
+                sn = 0;
+            }
+            else
+            {
+                if( (double)(b)==(double)(0) )
+                {
+                    
+                    //
+                    // Swap rows and columns
+                    //
+                    cs = 0;
+                    sn = 1;
+                    temp = d;
+                    d = a;
+                    a = temp;
+                    b = -c;
+                    c = 0;
+                }
+                else
+                {
+                    if( (double)(a-d)==(double)(0) && extschursigntoone(b)!=extschursigntoone(c) )
+                    {
+                        cs = 1;
+                        sn = 0;
+                    }
+                    else
+                    {
+                        temp = a-d;
+                        p = 0.5*temp;
+                        bcmax = Math.Max(Math.Abs(b), Math.Abs(c));
+                        bcmis = Math.Min(Math.Abs(b), Math.Abs(c))*extschursigntoone(b)*extschursigntoone(c);
+                        scl = Math.Max(Math.Abs(p), bcmax);
+                        z = p/scl*p+bcmax/scl*bcmis;
+                        
+                        //
+                        // If Z is of the order of the machine accuracy, postpone the
+                        // decision on the nature of eigenvalues
+                        //
+                        if( (double)(z)>=(double)(multpl*eps) )
+                        {
+                            
+                            //
+                            // Real eigenvalues. Compute A and D.
+                            //
+                            z = p+extschursign(Math.Sqrt(scl)*Math.Sqrt(z), p);
+                            a = d+z;
+                            d = d-bcmax/z*bcmis;
+                            
+                            //
+                            // Compute B and the rotation matrix
+                            //
+                            tau = blas.pythag2(c, z);
+                            cs = z/tau;
+                            sn = c/tau;
+                            b = b-c;
+                            c = 0;
+                        }
+                        else
+                        {
+                            
+                            //
+                            // Complex eigenvalues, or real (almost) equal eigenvalues.
+                            // Make diagonal elements equal.
+                            //
+                            sigma = b+c;
+                            tau = blas.pythag2(sigma, temp);
+                            cs = Math.Sqrt(0.5*(1+Math.Abs(sigma)/tau));
+                            sn = -(p/(tau*cs)*extschursign(1, sigma));
+                            
+                            //
+                            // Compute [ AA  BB ] = [ A  B ] [ CS -SN ]
+                            //         [ CC  DD ]   [ C  D ] [ SN  CS ]
+                            //
+                            aa = a*cs+b*sn;
+                            bb = -(a*sn)+b*cs;
+                            cc = c*cs+d*sn;
+                            dd = -(c*sn)+d*cs;
+                            
+                            //
+                            // Compute [ A  B ] = [ CS  SN ] [ AA  BB ]
+                            //         [ C  D ]   [-SN  CS ] [ CC  DD ]
+                            //
+                            a = aa*cs+cc*sn;
+                            b = bb*cs+dd*sn;
+                            c = -(aa*sn)+cc*cs;
+                            d = -(bb*sn)+dd*cs;
+                            temp = 0.5*(a+d);
+                            a = temp;
+                            d = temp;
+                            if( (double)(c)!=(double)(0) )
+                            {
+                                if( (double)(b)!=(double)(0) )
+                                {
+                                    if( extschursigntoone(b)==extschursigntoone(c) )
+                                    {
+                                        
+                                        //
+                                        // Real eigenvalues: reduce to upper triangular form
+                                        //
+                                        sab = Math.Sqrt(Math.Abs(b));
+                                        sac = Math.Sqrt(Math.Abs(c));
+                                        p = extschursign(sab*sac, c);
+                                        tau = 1/Math.Sqrt(Math.Abs(b+c));
+                                        a = temp+p;
+                                        d = temp-p;
+                                        b = b-c;
+                                        c = 0;
+                                        cs1 = sab*tau;
+                                        sn1 = sac*tau;
+                                        temp = cs*cs1-sn*sn1;
+                                        sn = cs*sn1+sn*cs1;
+                                        cs = temp;
+                                    }
+                                }
+                                else
+                                {
+                                    b = -c;
+                                    c = 0;
+                                    temp = cs;
+                                    cs = -sn;
+                                    sn = temp;
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            
+            //
+            // Store eigenvalues in (RT1R,RT1I) and (RT2R,RT2I).
+            //
+            rt1r = a;
+            rt2r = d;
+            if( (double)(c)==(double)(0) )
+            {
+                rt1i = 0;
+                rt2i = 0;
+            }
+            else
+            {
+                rt1i = Math.Sqrt(Math.Abs(b))*Math.Sqrt(Math.Abs(c));
+                rt2i = -rt1i;
+            }
+        }
+
+
+        private static double extschursign(double a,
+            double b)
+        {
+            double result = 0;
+
+            if( (double)(b)>=(double)(0) )
+            {
+                result = Math.Abs(a);
+            }
+            else
+            {
+                result = -Math.Abs(a);
+            }
+            return result;
+        }
+
+
+        private static int extschursigntoone(double b)
+        {
+            int result = 0;
+
+            if( (double)(b)>=(double)(0) )
+            {
+                result = 1;
+            }
+            else
+            {
+                result = -1;
+            }
+            return result;
+        }
+
+
+    }
+    public class trlinsolve
+    {
+        /*************************************************************************
+        Utility subroutine performing the "safe" solution of system of linear
+        equations with triangular coefficient matrices.
+
+        The subroutine uses scaling and solves the scaled system A*x=s*b (where  s
+        is  a  scalar  value)  instead  of  A*x=b,  choosing  s  so  that x can be
+        represented by a floating-point number. The closer the system  gets  to  a
+        singular, the less s is. If the system is singular, s=0 and x contains the
+        non-trivial solution of equation A*x=0.
+
+        The feature of an algorithm is that it could not cause an  overflow  or  a
+        division by zero regardless of the matrix used as the input.
+
+        The algorithm can solve systems of equations with  upper/lower  triangular
+        matrices,  with/without unit diagonal, and systems of type A*x=b or A'*x=b
+        (where A' is a transposed matrix A).
+
+        Input parameters:
+            A       -   system matrix. Array whose indexes range within [0..N-1, 0..N-1].
+            N       -   size of matrix A.
+            X       -   right-hand member of a system.
+                        Array whose index ranges within [0..N-1].
+            IsUpper -   matrix type. If it is True, the system matrix is the upper
+                        triangular and is located in  the  corresponding  part  of
+                        matrix A.
+            Trans   -   problem type. If it is True, the problem to be  solved  is
+                        A'*x=b, otherwise it is A*x=b.
+            Isunit  -   matrix type. If it is True, the system matrix has  a  unit
+                        diagonal (the elements on the main diagonal are  not  used
+                        in the calculation process), otherwise the matrix is considered
+                        to be a general triangular matrix.
+
+        Output parameters:
+            X       -   solution. Array whose index ranges within [0..N-1].
+            S       -   scaling factor.
+
+          -- LAPACK auxiliary routine (version 3.0) --
+             Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd.,
+             Courant Institute, Argonne National Lab, and Rice University
+             June 30, 1992
+        *************************************************************************/
+        public static void rmatrixtrsafesolve(double[,] a,
+            int n,
+            ref double[] x,
+            ref double s,
+            bool isupper,
+            bool istrans,
+            bool isunit)
+        {
+            bool normin = new bool();
+            double[] cnorm = new double[0];
+            double[,] a1 = new double[0,0];
+            double[] x1 = new double[0];
+            int i = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            s = 0;
+
+            
+            //
+            // From 0-based to 1-based
+            //
+            normin = false;
+            a1 = new double[n+1, n+1];
+            x1 = new double[n+1];
+            for(i=1; i<=n; i++)
+            {
+                i1_ = (0) - (1);
+                for(i_=1; i_<=n;i_++)
+                {
+                    a1[i,i_] = a[i-1,i_+i1_];
+                }
+            }
+            i1_ = (0) - (1);
+            for(i_=1; i_<=n;i_++)
+            {
+                x1[i_] = x[i_+i1_];
+            }
+            
+            //
+            // Solve 1-based
+            //
+            safesolvetriangular(a1, n, ref x1, ref s, isupper, istrans, isunit, normin, ref cnorm);
+            
+            //
+            // From 1-based to 0-based
+            //
+            i1_ = (1) - (0);
+            for(i_=0; i_<=n-1;i_++)
+            {
+                x[i_] = x1[i_+i1_];
+            }
+        }
+
+
+        /*************************************************************************
+        Obsolete 1-based subroutine.
+        See RMatrixTRSafeSolve for 0-based replacement.
+        *************************************************************************/
+        public static void safesolvetriangular(double[,] a,
+            int n,
+            ref double[] x,
+            ref double s,
+            bool isupper,
+            bool istrans,
+            bool isunit,
+            bool normin,
+            ref double[] cnorm)
+        {
+            int i = 0;
+            int imax = 0;
+            int j = 0;
+            int jfirst = 0;
+            int jinc = 0;
+            int jlast = 0;
+            int jm1 = 0;
+            int jp1 = 0;
+            int ip1 = 0;
+            int im1 = 0;
+            int k = 0;
+            int flg = 0;
+            double v = 0;
+            double vd = 0;
+            double bignum = 0;
+            double grow = 0;
+            double rec = 0;
+            double smlnum = 0;
+            double sumj = 0;
+            double tjj = 0;
+            double tjjs = 0;
+            double tmax = 0;
+            double tscal = 0;
+            double uscal = 0;
+            double xbnd = 0;
+            double xj = 0;
+            double xmax = 0;
+            bool notran = new bool();
+            bool upper = new bool();
+            bool nounit = new bool();
+            int i_ = 0;
+
+            s = 0;
+
+            upper = isupper;
+            notran = !istrans;
+            nounit = !isunit;
+            
+            //
+            // these initializers are not really necessary,
+            // but without them compiler complains about uninitialized locals
+            //
+            tjjs = 0;
+            
+            //
+            // Quick return if possible
+            //
+            if( n==0 )
+            {
+                return;
+            }
+            
+            //
+            // Determine machine dependent parameters to control overflow.
+            //
+            smlnum = math.minrealnumber/(math.machineepsilon*2);
+            bignum = 1/smlnum;
+            s = 1;
+            if( !normin )
+            {
+                cnorm = new double[n+1];
+                
+                //
+                // Compute the 1-norm of each column, not including the diagonal.
+                //
+                if( upper )
+                {
+                    
+                    //
+                    // A is upper triangular.
+                    //
+                    for(j=1; j<=n; j++)
+                    {
+                        v = 0;
+                        for(k=1; k<=j-1; k++)
+                        {
+                            v = v+Math.Abs(a[k,j]);
+                        }
+                        cnorm[j] = v;
+                    }
+                }
+                else
+                {
+                    
+                    //
+                    // A is lower triangular.
+                    //
+                    for(j=1; j<=n-1; j++)
+                    {
+                        v = 0;
+                        for(k=j+1; k<=n; k++)
+                        {
+                            v = v+Math.Abs(a[k,j]);
+                        }
+                        cnorm[j] = v;
+                    }
+                    cnorm[n] = 0;
+                }
+            }
+            
+            //
+            // Scale the column norms by TSCAL if the maximum element in CNORM is
+            // greater than BIGNUM.
+            //
+            imax = 1;
+            for(k=2; k<=n; k++)
+            {
+                if( (double)(cnorm[k])>(double)(cnorm[imax]) )
+                {
+                    imax = k;
+                }
+            }
+            tmax = cnorm[imax];
+            if( (double)(tmax)<=(double)(bignum) )
+            {
+                tscal = 1;
+            }
+            else
+            {
+                tscal = 1/(smlnum*tmax);
+                for(i_=1; i_<=n;i_++)
+                {
+                    cnorm[i_] = tscal*cnorm[i_];
+                }
+            }
+            
+            //
+            // Compute a bound on the computed solution vector to see if the
+            // Level 2 BLAS routine DTRSV can be used.
+            //
+            j = 1;
+            for(k=2; k<=n; k++)
+            {
+                if( (double)(Math.Abs(x[k]))>(double)(Math.Abs(x[j])) )
+                {
+                    j = k;
+                }
+            }
+            xmax = Math.Abs(x[j]);
+            xbnd = xmax;
+            if( notran )
+            {
+                
+                //
+                // Compute the growth in A * x = b.
+                //
+                if( upper )
+                {
+                    jfirst = n;
+                    jlast = 1;
+                    jinc = -1;
+                }
+                else
+                {
+                    jfirst = 1;
+                    jlast = n;
+                    jinc = 1;
+                }
+                if( (double)(tscal)!=(double)(1) )
+                {
+                    grow = 0;
+                }
+                else
+                {
+                    if( nounit )
+                    {
+                        
+                        //
+                        // A is non-unit triangular.
+                        //
+                        // Compute GROW = 1/G(j) and XBND = 1/M(j).
+                        // Initially, G(0) = max{x(i), i=1,...,n}.
+                        //
+                        grow = 1/Math.Max(xbnd, smlnum);
+                        xbnd = grow;
+                        j = jfirst;
+                        while( (jinc>0 && j<=jlast) || (jinc<0 && j>=jlast) )
+                        {
+                            
+                            //
+                            // Exit the loop if the growth factor is too small.
+                            //
+                            if( (double)(grow)<=(double)(smlnum) )
+                            {
+                                break;
+                            }
+                            
+                            //
+                            // M(j) = G(j-1) / abs(A(j,j))
+                            //
+                            tjj = Math.Abs(a[j,j]);
+                            xbnd = Math.Min(xbnd, Math.Min(1, tjj)*grow);
+                            if( (double)(tjj+cnorm[j])>=(double)(smlnum) )
+                            {
+                                
+                                //
+                                // G(j) = G(j-1)*( 1 + CNORM(j) / abs(A(j,j)) )
+                                //
+                                grow = grow*(tjj/(tjj+cnorm[j]));
+                            }
+                            else
+                            {
+                                
+                                //
+                                // G(j) could overflow, set GROW to 0.
+                                //
+                                grow = 0;
+                            }
+                            if( j==jlast )
+                            {
+                                grow = xbnd;
+                            }
+                            j = j+jinc;
+                        }
+                    }
+                    else
+                    {
+                        
+                        //
+                        // A is unit triangular.
+                        //
+                        // Compute GROW = 1/G(j), where G(0) = max{x(i), i=1,...,n}.
+                        //
+                        grow = Math.Min(1, 1/Math.Max(xbnd, smlnum));
+                        j = jfirst;
+                        while( (jinc>0 && j<=jlast) || (jinc<0 && j>=jlast) )
+                        {
+                            
+                            //
+                            // Exit the loop if the growth factor is too small.
+                            //
+                            if( (double)(grow)<=(double)(smlnum) )
+                            {
+                                break;
+                            }
+                            
+                            //
+                            // G(j) = G(j-1)*( 1 + CNORM(j) )
+                            //
+                            grow = grow*(1/(1+cnorm[j]));
+                            j = j+jinc;
+                        }
+                    }
+                }
+            }
+            else
+            {
+                
+                //
+                // Compute the growth in A' * x = b.
+                //
+                if( upper )
+                {
+                    jfirst = 1;
+                    jlast = n;
+                    jinc = 1;
+                }
+                else
+                {
+                    jfirst = n;
+                    jlast = 1;
+                    jinc = -1;
+                }
+                if( (double)(tscal)!=(double)(1) )
+                {
+                    grow = 0;
+                }
+                else
+                {
+                    if( nounit )
+                    {
+                        
+                        //
+                        // A is non-unit triangular.
+                        //
+                        // Compute GROW = 1/G(j) and XBND = 1/M(j).
+                        // Initially, M(0) = max{x(i), i=1,...,n}.
+                        //
+                        grow = 1/Math.Max(xbnd, smlnum);
+                        xbnd = grow;
+                        j = jfirst;
+                        while( (jinc>0 && j<=jlast) || (jinc<0 && j>=jlast) )
+                        {
+                            
+                            //
+                            // Exit the loop if the growth factor is too small.
+                            //
+                            if( (double)(grow)<=(double)(smlnum) )
+                            {
+                                break;
+                            }
+                            
+                            //
+                            // G(j) = max( G(j-1), M(j-1)*( 1 + CNORM(j) ) )
+                            //
+                            xj = 1+cnorm[j];
+                            grow = Math.Min(grow, xbnd/xj);
+                            
+                            //
+                            // M(j) = M(j-1)*( 1 + CNORM(j) ) / abs(A(j,j))
+                            //
+                            tjj = Math.Abs(a[j,j]);
+                            if( (double)(xj)>(double)(tjj) )
+                            {
+                                xbnd = xbnd*(tjj/xj);
+                            }
+                            if( j==jlast )
+                            {
+                                grow = Math.Min(grow, xbnd);
+                            }
+                            j = j+jinc;
+                        }
+                    }
+                    else
+                    {
+                        
+                        //
+                        // A is unit triangular.
+                        //
+                        // Compute GROW = 1/G(j), where G(0) = max{x(i), i=1,...,n}.
+                        //
+                        grow = Math.Min(1, 1/Math.Max(xbnd, smlnum));
+                        j = jfirst;
+                        while( (jinc>0 && j<=jlast) || (jinc<0 && j>=jlast) )
+                        {
+                            
+                            //
+                            // Exit the loop if the growth factor is too small.
+                            //
+                            if( (double)(grow)<=(double)(smlnum) )
+                            {
+                                break;
+                            }
+                            
+                            //
+                            // G(j) = ( 1 + CNORM(j) )*G(j-1)
+                            //
+                            xj = 1+cnorm[j];
+                            grow = grow/xj;
+                            j = j+jinc;
+                        }
+                    }
+                }
+            }
+            if( (double)(grow*tscal)>(double)(smlnum) )
+            {
+                
+                //
+                // Use the Level 2 BLAS solve if the reciprocal of the bound on
+                // elements of X is not too small.
+                //
+                if( (upper && notran) || (!upper && !notran) )
+                {
+                    if( nounit )
+                    {
+                        vd = a[n,n];
+                    }
+                    else
+                    {
+                        vd = 1;
+                    }
+                    x[n] = x[n]/vd;
+                    for(i=n-1; i>=1; i--)
+                    {
+                        ip1 = i+1;
+                        if( upper )
+                        {
+                            v = 0.0;
+                            for(i_=ip1; i_<=n;i_++)
+                            {
+                                v += a[i,i_]*x[i_];
+                            }
+                        }
+                        else
+                        {
+                            v = 0.0;
+                            for(i_=ip1; i_<=n;i_++)
+                            {
+                                v += a[i_,i]*x[i_];
+                            }
+                        }
+                        if( nounit )
+                        {
+                            vd = a[i,i];
+                        }
+                        else
+                        {
+                            vd = 1;
+                        }
+                        x[i] = (x[i]-v)/vd;
+                    }
+                }
+                else
+                {
+                    if( nounit )
+                    {
+                        vd = a[1,1];
+                    }
+                    else
+                    {
+                        vd = 1;
+                    }
+                    x[1] = x[1]/vd;
+                    for(i=2; i<=n; i++)
+                    {
+                        im1 = i-1;
+                        if( upper )
+                        {
+                            v = 0.0;
+                            for(i_=1; i_<=im1;i_++)
+                            {
+                                v += a[i_,i]*x[i_];
+                            }
+                        }
+                        else
+                        {
+                            v = 0.0;
+                            for(i_=1; i_<=im1;i_++)
+                            {
+                                v += a[i,i_]*x[i_];
+                            }
+                        }
+                        if( nounit )
+                        {
+                            vd = a[i,i];
+                        }
+                        else
+                        {
+                            vd = 1;
+                        }
+                        x[i] = (x[i]-v)/vd;
+                    }
+                }
+            }
+            else
+            {
+                
+                //
+                // Use a Level 1 BLAS solve, scaling intermediate results.
+                //
+                if( (double)(xmax)>(double)(bignum) )
+                {
+                    
+                    //
+                    // Scale X so that its components are less than or equal to
+                    // BIGNUM in absolute value.
+                    //
+                    s = bignum/xmax;
+                    for(i_=1; i_<=n;i_++)
+                    {
+                        x[i_] = s*x[i_];
+                    }
+                    xmax = bignum;
+                }
+                if( notran )
+                {
+                    
+                    //
+                    // Solve A * x = b
+                    //
+                    j = jfirst;
+                    while( (jinc>0 && j<=jlast) || (jinc<0 && j>=jlast) )
+                    {
+                        
+                        //
+                        // Compute x(j) = b(j) / A(j,j), scaling x if necessary.
+                        //
+                        xj = Math.Abs(x[j]);
+                        flg = 0;
+                        if( nounit )
+                        {
+                            tjjs = a[j,j]*tscal;
+                        }
+                        else
+                        {
+                            tjjs = tscal;
+                            if( (double)(tscal)==(double)(1) )
+                            {
+                                flg = 100;
+                            }
+                        }
+                        if( flg!=100 )
+                        {
+                            tjj = Math.Abs(tjjs);
+                            if( (double)(tjj)>(double)(smlnum) )
+                            {
+                                
+                                //
+                                // abs(A(j,j)) > SMLNUM:
+                                //
+                                if( (double)(tjj)<(double)(1) )
+                                {
+                                    if( (double)(xj)>(double)(tjj*bignum) )
+                                    {
+                                        
+                                        //
+                                        // Scale x by 1/b(j).
+                                        //
+                                        rec = 1/xj;
+                                        for(i_=1; i_<=n;i_++)
+                                        {
+                                            x[i_] = rec*x[i_];
+                                        }
+                                        s = s*rec;
+                                        xmax = xmax*rec;
+                                    }
+                                }
+                                x[j] = x[j]/tjjs;
+                                xj = Math.Abs(x[j]);
+                            }
+                            else
+                            {
+                                if( (double)(tjj)>(double)(0) )
+                                {
+                                    
+                                    //
+                                    // 0 < abs(A(j,j)) <= SMLNUM:
+                                    //
+                                    if( (double)(xj)>(double)(tjj*bignum) )
+                                    {
+                                        
+                                        //
+                                        // Scale x by (1/abs(x(j)))*abs(A(j,j))*BIGNUM
+                                        // to avoid overflow when dividing by A(j,j).
+                                        //
+                                        rec = tjj*bignum/xj;
+                                        if( (double)(cnorm[j])>(double)(1) )
+                                        {
+                                            
+                                            //
+                                            // Scale by 1/CNORM(j) to avoid overflow when
+                                            // multiplying x(j) times column j.
+                                            //
+                                            rec = rec/cnorm[j];
+                                        }
+                                        for(i_=1; i_<=n;i_++)
+                                        {
+                                            x[i_] = rec*x[i_];
+                                        }
+                                        s = s*rec;
+                                        xmax = xmax*rec;
+                                    }
+                                    x[j] = x[j]/tjjs;
+                                    xj = Math.Abs(x[j]);
+                                }
+                                else
+                                {
+                                    
+                                    //
+                                    // A(j,j) = 0:  Set x(1:n) = 0, x(j) = 1, and
+                                    // scale = 0, and compute a solution to A*x = 0.
+                                    //
+                                    for(i=1; i<=n; i++)
+                                    {
+                                        x[i] = 0;
+                                    }
+                                    x[j] = 1;
+                                    xj = 1;
+                                    s = 0;
+                                    xmax = 0;
+                                }
+                            }
+                        }
+                        
+                        //
+                        // Scale x if necessary to avoid overflow when adding a
+                        // multiple of column j of A.
+                        //
+                        if( (double)(xj)>(double)(1) )
+                        {
+                            rec = 1/xj;
+                            if( (double)(cnorm[j])>(double)((bignum-xmax)*rec) )
+                            {
+                                
+                                //
+                                // Scale x by 1/(2*abs(x(j))).
+                                //
+                                rec = rec*0.5;
+                                for(i_=1; i_<=n;i_++)
+                                {
+                                    x[i_] = rec*x[i_];
+                                }
+                                s = s*rec;
+                            }
+                        }
+                        else
+                        {
+                            if( (double)(xj*cnorm[j])>(double)(bignum-xmax) )
+                            {
+                                
+                                //
+                                // Scale x by 1/2.
+                                //
+                                for(i_=1; i_<=n;i_++)
+                                {
+                                    x[i_] = 0.5*x[i_];
+                                }
+                                s = s*0.5;
+                            }
+                        }
+                        if( upper )
+                        {
+                            if( j>1 )
+                            {
+                                
+                                //
+                                // Compute the update
+                                // x(1:j-1) := x(1:j-1) - x(j) * A(1:j-1,j)
+                                //
+                                v = x[j]*tscal;
+                                jm1 = j-1;
+                                for(i_=1; i_<=jm1;i_++)
+                                {
+                                    x[i_] = x[i_] - v*a[i_,j];
+                                }
+                                i = 1;
+                                for(k=2; k<=j-1; k++)
+                                {
+                                    if( (double)(Math.Abs(x[k]))>(double)(Math.Abs(x[i])) )
+                                    {
+                                        i = k;
+                                    }
+                                }
+                                xmax = Math.Abs(x[i]);
+                            }
+                        }
+                        else
+                        {
+                            if( j<n )
+                            {
+                                
+                                //
+                                // Compute the update
+                                // x(j+1:n) := x(j+1:n) - x(j) * A(j+1:n,j)
+                                //
+                                jp1 = j+1;
+                                v = x[j]*tscal;
+                                for(i_=jp1; i_<=n;i_++)
+                                {
+                                    x[i_] = x[i_] - v*a[i_,j];
+                                }
+                                i = j+1;
+                                for(k=j+2; k<=n; k++)
+                                {
+                                    if( (double)(Math.Abs(x[k]))>(double)(Math.Abs(x[i])) )
+                                    {
+                                        i = k;
+                                    }
+                                }
+                                xmax = Math.Abs(x[i]);
+                            }
+                        }
+                        j = j+jinc;
+                    }
+                }
+                else
+                {
+                    
+                    //
+                    // Solve A' * x = b
+                    //
+                    j = jfirst;
+                    while( (jinc>0 && j<=jlast) || (jinc<0 && j>=jlast) )
+                    {
+                        
+                        //
+                        // Compute x(j) = b(j) - sum A(k,j)*x(k).
+                        //   k<>j
+                        //
+                        xj = Math.Abs(x[j]);
+                        uscal = tscal;
+                        rec = 1/Math.Max(xmax, 1);
+                        if( (double)(cnorm[j])>(double)((bignum-xj)*rec) )
+                        {
+                            
+                            //
+                            // If x(j) could overflow, scale x by 1/(2*XMAX).
+                            //
+                            rec = rec*0.5;
+                            if( nounit )
+                            {
+                                tjjs = a[j,j]*tscal;
+                            }
+                            else
+                            {
+                                tjjs = tscal;
+                            }
+                            tjj = Math.Abs(tjjs);
+                            if( (double)(tjj)>(double)(1) )
+                            {
+                                
+                                //
+                                // Divide by A(j,j) when scaling x if A(j,j) > 1.
+                                //
+                                rec = Math.Min(1, rec*tjj);
+                                uscal = uscal/tjjs;
+                            }
+                            if( (double)(rec)<(double)(1) )
+                            {
+                                for(i_=1; i_<=n;i_++)
+                                {
+                                    x[i_] = rec*x[i_];
+                                }
+                                s = s*rec;
+                                xmax = xmax*rec;
+                            }
+                        }
+                        sumj = 0;
+                        if( (double)(uscal)==(double)(1) )
+                        {
+                            
+                            //
+                            // If the scaling needed for A in the dot product is 1,
+                            // call DDOT to perform the dot product.
+                            //
+                            if( upper )
+                            {
+                                if( j>1 )
+                                {
+                                    jm1 = j-1;
+                                    sumj = 0.0;
+                                    for(i_=1; i_<=jm1;i_++)
+                                    {
+                                        sumj += a[i_,j]*x[i_];
+                                    }
+                                }
+                                else
+                                {
+                                    sumj = 0;
+                                }
+                            }
+                            else
+                            {
+                                if( j<n )
+                                {
+                                    jp1 = j+1;
+                                    sumj = 0.0;
+                                    for(i_=jp1; i_<=n;i_++)
+                                    {
+                                        sumj += a[i_,j]*x[i_];
+                                    }
+                                }
+                            }
+                        }
+                        else
+                        {
+                            
+                            //
+                            // Otherwise, use in-line code for the dot product.
+                            //
+                            if( upper )
+                            {
+                                for(i=1; i<=j-1; i++)
+                                {
+                                    v = a[i,j]*uscal;
+                                    sumj = sumj+v*x[i];
+                                }
+                            }
+                            else
+                            {
+                                if( j<n )
+                                {
+                                    for(i=j+1; i<=n; i++)
+                                    {
+                                        v = a[i,j]*uscal;
+                                        sumj = sumj+v*x[i];
+                                    }
+                                }
+                            }
+                        }
+                        if( (double)(uscal)==(double)(tscal) )
+                        {
+                            
+                            //
+                            // Compute x(j) := ( x(j) - sumj ) / A(j,j) if 1/A(j,j)
+                            // was not used to scale the dotproduct.
+                            //
+                            x[j] = x[j]-sumj;
+                            xj = Math.Abs(x[j]);
+                            flg = 0;
+                            if( nounit )
+                            {
+                                tjjs = a[j,j]*tscal;
+                            }
+                            else
+                            {
+                                tjjs = tscal;
+                                if( (double)(tscal)==(double)(1) )
+                                {
+                                    flg = 150;
+                                }
+                            }
+                            
+                            //
+                            // Compute x(j) = x(j) / A(j,j), scaling if necessary.
+                            //
+                            if( flg!=150 )
+                            {
+                                tjj = Math.Abs(tjjs);
+                                if( (double)(tjj)>(double)(smlnum) )
+                                {
+                                    
+                                    //
+                                    // abs(A(j,j)) > SMLNUM:
+                                    //
+                                    if( (double)(tjj)<(double)(1) )
+                                    {
+                                        if( (double)(xj)>(double)(tjj*bignum) )
+                                        {
+                                            
+                                            //
+                                            // Scale X by 1/abs(x(j)).
+                                            //
+                                            rec = 1/xj;
+                                            for(i_=1; i_<=n;i_++)
+                                            {
+                                                x[i_] = rec*x[i_];
+                                            }
+                                            s = s*rec;
+                                            xmax = xmax*rec;
+                                        }
+                                    }
+                                    x[j] = x[j]/tjjs;
+                                }
+                                else
+                                {
+                                    if( (double)(tjj)>(double)(0) )
+                                    {
+                                        
+                                        //
+                                        // 0 < abs(A(j,j)) <= SMLNUM:
+                                        //
+                                        if( (double)(xj)>(double)(tjj*bignum) )
+                                        {
+                                            
+                                            //
+                                            // Scale x by (1/abs(x(j)))*abs(A(j,j))*BIGNUM.
+                                            //
+                                            rec = tjj*bignum/xj;
+                                            for(i_=1; i_<=n;i_++)
+                                            {
+                                                x[i_] = rec*x[i_];
+                                            }
+                                            s = s*rec;
+                                            xmax = xmax*rec;
+                                        }
+                                        x[j] = x[j]/tjjs;
+                                    }
+                                    else
+                                    {
+                                        
+                                        //
+                                        // A(j,j) = 0:  Set x(1:n) = 0, x(j) = 1, and
+                                        // scale = 0, and compute a solution to A'*x = 0.
+                                        //
+                                        for(i=1; i<=n; i++)
+                                        {
+                                            x[i] = 0;
+                                        }
+                                        x[j] = 1;
+                                        s = 0;
+                                        xmax = 0;
+                                    }
+                                }
+                            }
+                        }
+                        else
+                        {
+                            
+                            //
+                            // Compute x(j) := x(j) / A(j,j)  - sumj if the dot
+                            // product has already been divided by 1/A(j,j).
+                            //
+                            x[j] = x[j]/tjjs-sumj;
+                        }
+                        xmax = Math.Max(xmax, Math.Abs(x[j]));
+                        j = j+jinc;
+                    }
+                }
+                s = s/tscal;
+            }
+            
+            //
+            // Scale the column norms by 1/TSCAL for return.
+            //
+            if( (double)(tscal)!=(double)(1) )
+            {
+                v = 1/tscal;
+                for(i_=1; i_<=n;i_++)
+                {
+                    cnorm[i_] = v*cnorm[i_];
+                }
+            }
+        }
+
+
+    }
+    public class safesolve
+    {
+        /*************************************************************************
+        Real implementation of CMatrixScaledTRSafeSolve
+
+          -- ALGLIB routine --
+             21.01.2010
+             Bochkanov Sergey
+        *************************************************************************/
+        public static bool rmatrixscaledtrsafesolve(double[,] a,
+            double sa,
+            int n,
+            ref double[] x,
+            bool isupper,
+            int trans,
+            bool isunit,
+            double maxgrowth)
+        {
+            bool result = new bool();
+            double lnmax = 0;
+            double nrmb = 0;
+            double nrmx = 0;
+            int i = 0;
+            complex alpha = 0;
+            complex beta = 0;
+            double vr = 0;
+            complex cx = 0;
+            double[] tmp = new double[0];
+            int i_ = 0;
+
+            alglib.ap.assert(n>0, "RMatrixTRSafeSolve: incorrect N!");
+            alglib.ap.assert(trans==0 || trans==1, "RMatrixTRSafeSolve: incorrect Trans!");
+            result = true;
+            lnmax = Math.Log(math.maxrealnumber);
+            
+            //
+            // Quick return if possible
+            //
+            if( n<=0 )
+            {
+                return result;
+            }
+            
+            //
+            // Load norms: right part and X
+            //
+            nrmb = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                nrmb = Math.Max(nrmb, Math.Abs(x[i]));
+            }
+            nrmx = 0;
+            
+            //
+            // Solve
+            //
+            tmp = new double[n];
+            result = true;
+            if( isupper && trans==0 )
+            {
+                
+                //
+                // U*x = b
+                //
+                for(i=n-1; i>=0; i--)
+                {
+                    
+                    //
+                    // Task is reduced to alpha*x[i] = beta
+                    //
+                    if( isunit )
+                    {
+                        alpha = sa;
+                    }
+                    else
+                    {
+                        alpha = a[i,i]*sa;
+                    }
+                    if( i<n-1 )
+                    {
+                        for(i_=i+1; i_<=n-1;i_++)
+                        {
+                            tmp[i_] = sa*a[i,i_];
+                        }
+                        vr = 0.0;
+                        for(i_=i+1; i_<=n-1;i_++)
+                        {
+                            vr += tmp[i_]*x[i_];
+                        }
+                        beta = x[i]-vr;
+                    }
+                    else
+                    {
+                        beta = x[i];
+                    }
+                    
+                    //
+                    // solve alpha*x[i] = beta
+                    //
+                    result = cbasicsolveandupdate(alpha, beta, lnmax, nrmb, maxgrowth, ref nrmx, ref cx);
+                    if( !result )
+                    {
+                        return result;
+                    }
+                    x[i] = cx.x;
+                }
+                return result;
+            }
+            if( !isupper && trans==0 )
+            {
+                
+                //
+                // L*x = b
+                //
+                for(i=0; i<=n-1; i++)
+                {
+                    
+                    //
+                    // Task is reduced to alpha*x[i] = beta
+                    //
+                    if( isunit )
+                    {
+                        alpha = sa;
+                    }
+                    else
+                    {
+                        alpha = a[i,i]*sa;
+                    }
+                    if( i>0 )
+                    {
+                        for(i_=0; i_<=i-1;i_++)
+                        {
+                            tmp[i_] = sa*a[i,i_];
+                        }
+                        vr = 0.0;
+                        for(i_=0; i_<=i-1;i_++)
+                        {
+                            vr += tmp[i_]*x[i_];
+                        }
+                        beta = x[i]-vr;
+                    }
+                    else
+                    {
+                        beta = x[i];
+                    }
+                    
+                    //
+                    // solve alpha*x[i] = beta
+                    //
+                    result = cbasicsolveandupdate(alpha, beta, lnmax, nrmb, maxgrowth, ref nrmx, ref cx);
+                    if( !result )
+                    {
+                        return result;
+                    }
+                    x[i] = cx.x;
+                }
+                return result;
+            }
+            if( isupper && trans==1 )
+            {
+                
+                //
+                // U^T*x = b
+                //
+                for(i=0; i<=n-1; i++)
+                {
+                    
+                    //
+                    // Task is reduced to alpha*x[i] = beta
+                    //
+                    if( isunit )
+                    {
+                        alpha = sa;
+                    }
+                    else
+                    {
+                        alpha = a[i,i]*sa;
+                    }
+                    beta = x[i];
+                    
+                    //
+                    // solve alpha*x[i] = beta
+                    //
+                    result = cbasicsolveandupdate(alpha, beta, lnmax, nrmb, maxgrowth, ref nrmx, ref cx);
+                    if( !result )
+                    {
+                        return result;
+                    }
+                    x[i] = cx.x;
+                    
+                    //
+                    // update the rest of right part
+                    //
+                    if( i<n-1 )
+                    {
+                        vr = cx.x;
+                        for(i_=i+1; i_<=n-1;i_++)
+                        {
+                            tmp[i_] = sa*a[i,i_];
+                        }
+                        for(i_=i+1; i_<=n-1;i_++)
+                        {
+                            x[i_] = x[i_] - vr*tmp[i_];
+                        }
+                    }
+                }
+                return result;
+            }
+            if( !isupper && trans==1 )
+            {
+                
+                //
+                // L^T*x = b
+                //
+                for(i=n-1; i>=0; i--)
+                {
+                    
+                    //
+                    // Task is reduced to alpha*x[i] = beta
+                    //
+                    if( isunit )
+                    {
+                        alpha = sa;
+                    }
+                    else
+                    {
+                        alpha = a[i,i]*sa;
+                    }
+                    beta = x[i];
+                    
+                    //
+                    // solve alpha*x[i] = beta
+                    //
+                    result = cbasicsolveandupdate(alpha, beta, lnmax, nrmb, maxgrowth, ref nrmx, ref cx);
+                    if( !result )
+                    {
+                        return result;
+                    }
+                    x[i] = cx.x;
+                    
+                    //
+                    // update the rest of right part
+                    //
+                    if( i>0 )
+                    {
+                        vr = cx.x;
+                        for(i_=0; i_<=i-1;i_++)
+                        {
+                            tmp[i_] = sa*a[i,i_];
+                        }
+                        for(i_=0; i_<=i-1;i_++)
+                        {
+                            x[i_] = x[i_] - vr*tmp[i_];
+                        }
+                    }
+                }
+                return result;
+            }
+            result = false;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Internal subroutine for safe solution of
+
+            SA*op(A)=b
+            
+        where  A  is  NxN  upper/lower  triangular/unitriangular  matrix, op(A) is
+        either identity transform, transposition or Hermitian transposition, SA is
+        a scaling factor such that max(|SA*A[i,j]|) is close to 1.0 in magnutude.
+
+        This subroutine  limits  relative  growth  of  solution  (in inf-norm)  by
+        MaxGrowth,  returning  False  if  growth  exceeds MaxGrowth. Degenerate or
+        near-degenerate matrices are handled correctly (False is returned) as long
+        as MaxGrowth is significantly less than MaxRealNumber/norm(b).
+
+          -- ALGLIB routine --
+             21.01.2010
+             Bochkanov Sergey
+        *************************************************************************/
+        public static bool cmatrixscaledtrsafesolve(complex[,] a,
+            double sa,
+            int n,
+            ref complex[] x,
+            bool isupper,
+            int trans,
+            bool isunit,
+            double maxgrowth)
+        {
+            bool result = new bool();
+            double lnmax = 0;
+            double nrmb = 0;
+            double nrmx = 0;
+            int i = 0;
+            complex alpha = 0;
+            complex beta = 0;
+            complex vc = 0;
+            complex[] tmp = new complex[0];
+            int i_ = 0;
+
+            alglib.ap.assert(n>0, "CMatrixTRSafeSolve: incorrect N!");
+            alglib.ap.assert((trans==0 || trans==1) || trans==2, "CMatrixTRSafeSolve: incorrect Trans!");
+            result = true;
+            lnmax = Math.Log(math.maxrealnumber);
+            
+            //
+            // Quick return if possible
+            //
+            if( n<=0 )
+            {
+                return result;
+            }
+            
+            //
+            // Load norms: right part and X
+            //
+            nrmb = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                nrmb = Math.Max(nrmb, math.abscomplex(x[i]));
+            }
+            nrmx = 0;
+            
+            //
+            // Solve
+            //
+            tmp = new complex[n];
+            result = true;
+            if( isupper && trans==0 )
+            {
+                
+                //
+                // U*x = b
+                //
+                for(i=n-1; i>=0; i--)
+                {
+                    
+                    //
+                    // Task is reduced to alpha*x[i] = beta
+                    //
+                    if( isunit )
+                    {
+                        alpha = sa;
+                    }
+                    else
+                    {
+                        alpha = a[i,i]*sa;
+                    }
+                    if( i<n-1 )
+                    {
+                        for(i_=i+1; i_<=n-1;i_++)
+                        {
+                            tmp[i_] = sa*a[i,i_];
+                        }
+                        vc = 0.0;
+                        for(i_=i+1; i_<=n-1;i_++)
+                        {
+                            vc += tmp[i_]*x[i_];
+                        }
+                        beta = x[i]-vc;
+                    }
+                    else
+                    {
+                        beta = x[i];
+                    }
+                    
+                    //
+                    // solve alpha*x[i] = beta
+                    //
+                    result = cbasicsolveandupdate(alpha, beta, lnmax, nrmb, maxgrowth, ref nrmx, ref vc);
+                    if( !result )
+                    {
+                        return result;
+                    }
+                    x[i] = vc;
+                }
+                return result;
+            }
+            if( !isupper && trans==0 )
+            {
+                
+                //
+                // L*x = b
+                //
+                for(i=0; i<=n-1; i++)
+                {
+                    
+                    //
+                    // Task is reduced to alpha*x[i] = beta
+                    //
+                    if( isunit )
+                    {
+                        alpha = sa;
+                    }
+                    else
+                    {
+                        alpha = a[i,i]*sa;
+                    }
+                    if( i>0 )
+                    {
+                        for(i_=0; i_<=i-1;i_++)
+                        {
+                            tmp[i_] = sa*a[i,i_];
+                        }
+                        vc = 0.0;
+                        for(i_=0; i_<=i-1;i_++)
+                        {
+                            vc += tmp[i_]*x[i_];
+                        }
+                        beta = x[i]-vc;
+                    }
+                    else
+                    {
+                        beta = x[i];
+                    }
+                    
+                    //
+                    // solve alpha*x[i] = beta
+                    //
+                    result = cbasicsolveandupdate(alpha, beta, lnmax, nrmb, maxgrowth, ref nrmx, ref vc);
+                    if( !result )
+                    {
+                        return result;
+                    }
+                    x[i] = vc;
+                }
+                return result;
+            }
+            if( isupper && trans==1 )
+            {
+                
+                //
+                // U^T*x = b
+                //
+                for(i=0; i<=n-1; i++)
+                {
+                    
+                    //
+                    // Task is reduced to alpha*x[i] = beta
+                    //
+                    if( isunit )
+                    {
+                        alpha = sa;
+                    }
+                    else
+                    {
+                        alpha = a[i,i]*sa;
+                    }
+                    beta = x[i];
+                    
+                    //
+                    // solve alpha*x[i] = beta
+                    //
+                    result = cbasicsolveandupdate(alpha, beta, lnmax, nrmb, maxgrowth, ref nrmx, ref vc);
+                    if( !result )
+                    {
+                        return result;
+                    }
+                    x[i] = vc;
+                    
+                    //
+                    // update the rest of right part
+                    //
+                    if( i<n-1 )
+                    {
+                        for(i_=i+1; i_<=n-1;i_++)
+                        {
+                            tmp[i_] = sa*a[i,i_];
+                        }
+                        for(i_=i+1; i_<=n-1;i_++)
+                        {
+                            x[i_] = x[i_] - vc*tmp[i_];
+                        }
+                    }
+                }
+                return result;
+            }
+            if( !isupper && trans==1 )
+            {
+                
+                //
+                // L^T*x = b
+                //
+                for(i=n-1; i>=0; i--)
+                {
+                    
+                    //
+                    // Task is reduced to alpha*x[i] = beta
+                    //
+                    if( isunit )
+                    {
+                        alpha = sa;
+                    }
+                    else
+                    {
+                        alpha = a[i,i]*sa;
+                    }
+                    beta = x[i];
+                    
+                    //
+                    // solve alpha*x[i] = beta
+                    //
+                    result = cbasicsolveandupdate(alpha, beta, lnmax, nrmb, maxgrowth, ref nrmx, ref vc);
+                    if( !result )
+                    {
+                        return result;
+                    }
+                    x[i] = vc;
+                    
+                    //
+                    // update the rest of right part
+                    //
+                    if( i>0 )
+                    {
+                        for(i_=0; i_<=i-1;i_++)
+                        {
+                            tmp[i_] = sa*a[i,i_];
+                        }
+                        for(i_=0; i_<=i-1;i_++)
+                        {
+                            x[i_] = x[i_] - vc*tmp[i_];
+                        }
+                    }
+                }
+                return result;
+            }
+            if( isupper && trans==2 )
+            {
+                
+                //
+                // U^H*x = b
+                //
+                for(i=0; i<=n-1; i++)
+                {
+                    
+                    //
+                    // Task is reduced to alpha*x[i] = beta
+                    //
+                    if( isunit )
+                    {
+                        alpha = sa;
+                    }
+                    else
+                    {
+                        alpha = math.conj(a[i,i])*sa;
+                    }
+                    beta = x[i];
+                    
+                    //
+                    // solve alpha*x[i] = beta
+                    //
+                    result = cbasicsolveandupdate(alpha, beta, lnmax, nrmb, maxgrowth, ref nrmx, ref vc);
+                    if( !result )
+                    {
+                        return result;
+                    }
+                    x[i] = vc;
+                    
+                    //
+                    // update the rest of right part
+                    //
+                    if( i<n-1 )
+                    {
+                        for(i_=i+1; i_<=n-1;i_++)
+                        {
+                            tmp[i_] = sa*math.conj(a[i,i_]);
+                        }
+                        for(i_=i+1; i_<=n-1;i_++)
+                        {
+                            x[i_] = x[i_] - vc*tmp[i_];
+                        }
+                    }
+                }
+                return result;
+            }
+            if( !isupper && trans==2 )
+            {
+                
+                //
+                // L^T*x = b
+                //
+                for(i=n-1; i>=0; i--)
+                {
+                    
+                    //
+                    // Task is reduced to alpha*x[i] = beta
+                    //
+                    if( isunit )
+                    {
+                        alpha = sa;
+                    }
+                    else
+                    {
+                        alpha = math.conj(a[i,i])*sa;
+                    }
+                    beta = x[i];
+                    
+                    //
+                    // solve alpha*x[i] = beta
+                    //
+                    result = cbasicsolveandupdate(alpha, beta, lnmax, nrmb, maxgrowth, ref nrmx, ref vc);
+                    if( !result )
+                    {
+                        return result;
+                    }
+                    x[i] = vc;
+                    
+                    //
+                    // update the rest of right part
+                    //
+                    if( i>0 )
+                    {
+                        for(i_=0; i_<=i-1;i_++)
+                        {
+                            tmp[i_] = sa*math.conj(a[i,i_]);
+                        }
+                        for(i_=0; i_<=i-1;i_++)
+                        {
+                            x[i_] = x[i_] - vc*tmp[i_];
+                        }
+                    }
+                }
+                return result;
+            }
+            result = false;
+            return result;
+        }
+
+
+        /*************************************************************************
+        complex basic solver-updater for reduced linear system
+
+            alpha*x[i] = beta
+
+        solves this equation and updates it in overlfow-safe manner (keeping track
+        of relative growth of solution).
+
+        Parameters:
+            Alpha   -   alpha
+            Beta    -   beta
+            LnMax   -   precomputed Ln(MaxRealNumber)
+            BNorm   -   inf-norm of b (right part of original system)
+            MaxGrowth-  maximum growth of norm(x) relative to norm(b)
+            XNorm   -   inf-norm of other components of X (which are already processed)
+                        it is updated by CBasicSolveAndUpdate.
+            X       -   solution
+
+          -- ALGLIB routine --
+             26.01.2009
+             Bochkanov Sergey
+        *************************************************************************/
+        private static bool cbasicsolveandupdate(complex alpha,
+            complex beta,
+            double lnmax,
+            double bnorm,
+            double maxgrowth,
+            ref double xnorm,
+            ref complex x)
+        {
+            bool result = new bool();
+            double v = 0;
+
+            x = 0;
+
+            result = false;
+            if( alpha==0 )
+            {
+                return result;
+            }
+            if( beta!=0 )
+            {
+                
+                //
+                // alpha*x[i]=beta
+                //
+                v = Math.Log(math.abscomplex(beta))-Math.Log(math.abscomplex(alpha));
+                if( (double)(v)>(double)(lnmax) )
+                {
+                    return result;
+                }
+                x = beta/alpha;
+            }
+            else
+            {
+                
+                //
+                // alpha*x[i]=0
+                //
+                x = 0;
+            }
+            
+            //
+            // update NrmX, test growth limit
+            //
+            xnorm = Math.Max(xnorm, math.abscomplex(x));
+            if( (double)(xnorm)>(double)(maxgrowth*bnorm) )
+            {
+                return result;
+            }
+            result = true;
+            return result;
+        }
+
+
+    }
+    public class xblas
+    {
+        /*************************************************************************
+        More precise dot-product. Absolute error of  subroutine  result  is  about
+        1 ulp of max(MX,V), where:
+            MX = max( |a[i]*b[i]| )
+            V  = |(a,b)|
+
+        INPUT PARAMETERS
+            A       -   array[0..N-1], vector 1
+            B       -   array[0..N-1], vector 2
+            N       -   vectors length, N<2^29.
+            Temp    -   array[0..N-1], pre-allocated temporary storage
+
+        OUTPUT PARAMETERS
+            R       -   (A,B)
+            RErr    -   estimate of error. This estimate accounts for both  errors
+                        during  calculation  of  (A,B)  and  errors  introduced by
+                        rounding of A and B to fit in double (about 1 ulp).
+
+          -- ALGLIB --
+             Copyright 24.08.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void xdot(double[] a,
+            double[] b,
+            int n,
+            ref double[] temp,
+            ref double r,
+            ref double rerr)
+        {
+            int i = 0;
+            double mx = 0;
+            double v = 0;
+
+            r = 0;
+            rerr = 0;
+
+            
+            //
+            // special cases:
+            // * N=0
+            //
+            if( n==0 )
+            {
+                r = 0;
+                rerr = 0;
+                return;
+            }
+            mx = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                v = a[i]*b[i];
+                temp[i] = v;
+                mx = Math.Max(mx, Math.Abs(v));
+            }
+            if( (double)(mx)==(double)(0) )
+            {
+                r = 0;
+                rerr = 0;
+                return;
+            }
+            xsum(ref temp, mx, n, ref r, ref rerr);
+        }
+
+
+        /*************************************************************************
+        More precise complex dot-product. Absolute error of  subroutine  result is
+        about 1 ulp of max(MX,V), where:
+            MX = max( |a[i]*b[i]| )
+            V  = |(a,b)|
+
+        INPUT PARAMETERS
+            A       -   array[0..N-1], vector 1
+            B       -   array[0..N-1], vector 2
+            N       -   vectors length, N<2^29.
+            Temp    -   array[0..2*N-1], pre-allocated temporary storage
+
+        OUTPUT PARAMETERS
+            R       -   (A,B)
+            RErr    -   estimate of error. This estimate accounts for both  errors
+                        during  calculation  of  (A,B)  and  errors  introduced by
+                        rounding of A and B to fit in double (about 1 ulp).
+
+          -- ALGLIB --
+             Copyright 27.01.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void xcdot(complex[] a,
+            complex[] b,
+            int n,
+            ref double[] temp,
+            ref complex r,
+            ref double rerr)
+        {
+            int i = 0;
+            double mx = 0;
+            double v = 0;
+            double rerrx = 0;
+            double rerry = 0;
+
+            r = 0;
+            rerr = 0;
+
+            
+            //
+            // special cases:
+            // * N=0
+            //
+            if( n==0 )
+            {
+                r = 0;
+                rerr = 0;
+                return;
+            }
+            
+            //
+            // calculate real part
+            //
+            mx = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                v = a[i].x*b[i].x;
+                temp[2*i+0] = v;
+                mx = Math.Max(mx, Math.Abs(v));
+                v = -(a[i].y*b[i].y);
+                temp[2*i+1] = v;
+                mx = Math.Max(mx, Math.Abs(v));
+            }
+            if( (double)(mx)==(double)(0) )
+            {
+                r.x = 0;
+                rerrx = 0;
+            }
+            else
+            {
+                xsum(ref temp, mx, 2*n, ref r.x, ref rerrx);
+            }
+            
+            //
+            // calculate imaginary part
+            //
+            mx = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                v = a[i].x*b[i].y;
+                temp[2*i+0] = v;
+                mx = Math.Max(mx, Math.Abs(v));
+                v = a[i].y*b[i].x;
+                temp[2*i+1] = v;
+                mx = Math.Max(mx, Math.Abs(v));
+            }
+            if( (double)(mx)==(double)(0) )
+            {
+                r.y = 0;
+                rerry = 0;
+            }
+            else
+            {
+                xsum(ref temp, mx, 2*n, ref r.y, ref rerry);
+            }
+            
+            //
+            // total error
+            //
+            if( (double)(rerrx)==(double)(0) && (double)(rerry)==(double)(0) )
+            {
+                rerr = 0;
+            }
+            else
+            {
+                rerr = Math.Max(rerrx, rerry)*Math.Sqrt(1+math.sqr(Math.Min(rerrx, rerry)/Math.Max(rerrx, rerry)));
+            }
+        }
+
+
+        /*************************************************************************
+        Internal subroutine for extra-precise calculation of SUM(w[i]).
+
+        INPUT PARAMETERS:
+            W   -   array[0..N-1], values to be added
+                    W is modified during calculations.
+            MX  -   max(W[i])
+            N   -   array size
+            
+        OUTPUT PARAMETERS:
+            R   -   SUM(w[i])
+            RErr-   error estimate for R
+
+          -- ALGLIB --
+             Copyright 24.08.2009 by Bochkanov Sergey
+        *************************************************************************/
+        private static void xsum(ref double[] w,
+            double mx,
+            int n,
+            ref double r,
+            ref double rerr)
+        {
+            int i = 0;
+            int k = 0;
+            int ks = 0;
+            double v = 0;
+            double s = 0;
+            double ln2 = 0;
+            double chunk = 0;
+            double invchunk = 0;
+            bool allzeros = new bool();
+            int i_ = 0;
+
+            r = 0;
+            rerr = 0;
+
+            
+            //
+            // special cases:
+            // * N=0
+            // * N is too large to use integer arithmetics
+            //
+            if( n==0 )
+            {
+                r = 0;
+                rerr = 0;
+                return;
+            }
+            if( (double)(mx)==(double)(0) )
+            {
+                r = 0;
+                rerr = 0;
+                return;
+            }
+            alglib.ap.assert(n<536870912, "XDot: N is too large!");
+            
+            //
+            // Prepare
+            //
+            ln2 = Math.Log(2);
+            rerr = mx*math.machineepsilon;
+            
+            //
+            // 1. find S such that 0.5<=S*MX<1
+            // 2. multiply W by S, so task is normalized in some sense
+            // 3. S:=1/S so we can obtain original vector multiplying by S
+            //
+            k = (int)Math.Round(Math.Log(mx)/ln2);
+            s = xfastpow(2, -k);
+            while( (double)(s*mx)>=(double)(1) )
+            {
+                s = 0.5*s;
+            }
+            while( (double)(s*mx)<(double)(0.5) )
+            {
+                s = 2*s;
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                w[i_] = s*w[i_];
+            }
+            s = 1/s;
+            
+            //
+            // find Chunk=2^M such that N*Chunk<2^29
+            //
+            // we have chosen upper limit (2^29) with enough space left
+            // to tolerate possible problems with rounding and N's close
+            // to the limit, so we don't want to be very strict here.
+            //
+            k = (int)(Math.Log((double)536870912/(double)n)/ln2);
+            chunk = xfastpow(2, k);
+            if( (double)(chunk)<(double)(2) )
+            {
+                chunk = 2;
+            }
+            invchunk = 1/chunk;
+            
+            //
+            // calculate result
+            //
+            r = 0;
+            for(i_=0; i_<=n-1;i_++)
+            {
+                w[i_] = chunk*w[i_];
+            }
+            while( true )
+            {
+                s = s*invchunk;
+                allzeros = true;
+                ks = 0;
+                for(i=0; i<=n-1; i++)
+                {
+                    v = w[i];
+                    k = (int)(v);
+                    if( (double)(v)!=(double)(k) )
+                    {
+                        allzeros = false;
+                    }
+                    w[i] = chunk*(v-k);
+                    ks = ks+k;
+                }
+                r = r+s*ks;
+                v = Math.Abs(r);
+                if( allzeros || (double)(s*n+mx)==(double)(mx) )
+                {
+                    break;
+                }
+            }
+            
+            //
+            // correct error
+            //
+            rerr = Math.Max(rerr, Math.Abs(r)*math.machineepsilon);
+        }
+
+
+        /*************************************************************************
+        Fast Pow
+
+          -- ALGLIB --
+             Copyright 24.08.2009 by Bochkanov Sergey
+        *************************************************************************/
+        private static double xfastpow(double r,
+            int n)
+        {
+            double result = 0;
+
+            result = 0;
+            if( n>0 )
+            {
+                if( n%2==0 )
+                {
+                    result = math.sqr(xfastpow(r, n/2));
+                }
+                else
+                {
+                    result = r*xfastpow(r, n-1);
+                }
+                return result;
+            }
+            if( n==0 )
+            {
+                result = 1;
+            }
+            if( n<0 )
+            {
+                result = xfastpow(1/r, -n);
+            }
+            return result;
+        }
+
+
+    }
+    public class linmin
+    {
+        public class linminstate
+        {
+            public bool brackt;
+            public bool stage1;
+            public int infoc;
+            public double dg;
+            public double dgm;
+            public double dginit;
+            public double dgtest;
+            public double dgx;
+            public double dgxm;
+            public double dgy;
+            public double dgym;
+            public double finit;
+            public double ftest1;
+            public double fm;
+            public double fx;
+            public double fxm;
+            public double fy;
+            public double fym;
+            public double stx;
+            public double sty;
+            public double stmin;
+            public double stmax;
+            public double width;
+            public double width1;
+            public double xtrapf;
+        };
+
+
+        public class armijostate
+        {
+            public bool needf;
+            public double[] x;
+            public double f;
+            public int n;
+            public double[] xbase;
+            public double[] s;
+            public double stplen;
+            public double fcur;
+            public double stpmax;
+            public int fmax;
+            public int nfev;
+            public int info;
+            public rcommstate rstate;
+            public armijostate()
+            {
+                x = new double[0];
+                xbase = new double[0];
+                s = new double[0];
+                rstate = new rcommstate();
+            }
+        };
+
+
+
+
+        public const double ftol = 0.001;
+        public const double xtol = 100*math.machineepsilon;
+        public const int maxfev = 20;
+        public const double stpmin = 1.0E-50;
+        public const double defstpmax = 1.0E+50;
+        public const double armijofactor = 1.3;
+
+
+        /*************************************************************************
+        Normalizes direction/step pair: makes |D|=1, scales Stp.
+        If |D|=0, it returns, leavind D/Stp unchanged.
+
+          -- ALGLIB --
+             Copyright 01.04.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void linminnormalized(ref double[] d,
+            ref double stp,
+            int n)
+        {
+            double mx = 0;
+            double s = 0;
+            int i = 0;
+            int i_ = 0;
+
+            
+            //
+            // first, scale D to avoid underflow/overflow durng squaring
+            //
+            mx = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                mx = Math.Max(mx, Math.Abs(d[i]));
+            }
+            if( (double)(mx)==(double)(0) )
+            {
+                return;
+            }
+            s = 1/mx;
+            for(i_=0; i_<=n-1;i_++)
+            {
+                d[i_] = s*d[i_];
+            }
+            stp = stp/s;
+            
+            //
+            // normalize D
+            //
+            s = 0.0;
+            for(i_=0; i_<=n-1;i_++)
+            {
+                s += d[i_]*d[i_];
+            }
+            s = 1/Math.Sqrt(s);
+            for(i_=0; i_<=n-1;i_++)
+            {
+                d[i_] = s*d[i_];
+            }
+            stp = stp/s;
+        }
+
+
+        /*************************************************************************
+        THE  PURPOSE  OF  MCSRCH  IS  TO  FIND A STEP WHICH SATISFIES A SUFFICIENT
+        DECREASE CONDITION AND A CURVATURE CONDITION.
+
+        AT EACH STAGE THE SUBROUTINE  UPDATES  AN  INTERVAL  OF  UNCERTAINTY  WITH
+        ENDPOINTS  STX  AND  STY.  THE INTERVAL OF UNCERTAINTY IS INITIALLY CHOSEN
+        SO THAT IT CONTAINS A MINIMIZER OF THE MODIFIED FUNCTION
+
+            F(X+STP*S) - F(X) - FTOL*STP*(GRADF(X)'S).
+
+        IF  A STEP  IS OBTAINED FOR  WHICH THE MODIFIED FUNCTION HAS A NONPOSITIVE
+        FUNCTION  VALUE  AND  NONNEGATIVE  DERIVATIVE,   THEN   THE   INTERVAL  OF
+        UNCERTAINTY IS CHOSEN SO THAT IT CONTAINS A MINIMIZER OF F(X+STP*S).
+
+        THE  ALGORITHM  IS  DESIGNED TO FIND A STEP WHICH SATISFIES THE SUFFICIENT
+        DECREASE CONDITION
+
+            F(X+STP*S) .LE. F(X) + FTOL*STP*(GRADF(X)'S),
+
+        AND THE CURVATURE CONDITION
+
+            ABS(GRADF(X+STP*S)'S)) .LE. GTOL*ABS(GRADF(X)'S).
+
+        IF  FTOL  IS  LESS  THAN GTOL AND IF, FOR EXAMPLE, THE FUNCTION IS BOUNDED
+        BELOW,  THEN  THERE  IS  ALWAYS  A  STEP  WHICH SATISFIES BOTH CONDITIONS.
+        IF  NO  STEP  CAN BE FOUND  WHICH  SATISFIES  BOTH  CONDITIONS,  THEN  THE
+        ALGORITHM  USUALLY STOPS  WHEN  ROUNDING ERRORS  PREVENT FURTHER PROGRESS.
+        IN THIS CASE STP ONLY SATISFIES THE SUFFICIENT DECREASE CONDITION.
+
+
+        :::::::::::::IMPORTANT NOTES:::::::::::::
+
+        NOTE 1:
+
+        This routine  guarantees that it will stop at the last point where function
+        value was calculated. It won't make several additional function evaluations
+        after finding good point. So if you store function evaluations requested by
+        this routine, you can be sure that last one is the point where we've stopped.
+
+        NOTE 2:
+
+        when 0<StpMax<StpMin, algorithm will terminate with INFO=5 and Stp=0.0
+        :::::::::::::::::::::::::::::::::::::::::
+
+
+        PARAMETERS DESCRIPRION
+
+        STAGE IS ZERO ON FIRST CALL, ZERO ON FINAL EXIT
+
+        N IS A POSITIVE INTEGER INPUT VARIABLE SET TO THE NUMBER OF VARIABLES.
+
+        X IS  AN  ARRAY  OF  LENGTH N. ON INPUT IT MUST CONTAIN THE BASE POINT FOR
+        THE LINE SEARCH. ON OUTPUT IT CONTAINS X+STP*S.
+
+        F IS  A  VARIABLE. ON INPUT IT MUST CONTAIN THE VALUE OF F AT X. ON OUTPUT
+        IT CONTAINS THE VALUE OF F AT X + STP*S.
+
+        G IS AN ARRAY OF LENGTH N. ON INPUT IT MUST CONTAIN THE GRADIENT OF F AT X.
+        ON OUTPUT IT CONTAINS THE GRADIENT OF F AT X + STP*S.
+
+        S IS AN INPUT ARRAY OF LENGTH N WHICH SPECIFIES THE SEARCH DIRECTION.
+
+        STP  IS  A NONNEGATIVE VARIABLE. ON INPUT STP CONTAINS AN INITIAL ESTIMATE
+        OF A SATISFACTORY STEP. ON OUTPUT STP CONTAINS THE FINAL ESTIMATE.
+
+        FTOL AND GTOL ARE NONNEGATIVE INPUT VARIABLES. TERMINATION OCCURS WHEN THE
+        SUFFICIENT DECREASE CONDITION AND THE DIRECTIONAL DERIVATIVE CONDITION ARE
+        SATISFIED.
+
+        XTOL IS A NONNEGATIVE INPUT VARIABLE. TERMINATION OCCURS WHEN THE RELATIVE
+        WIDTH OF THE INTERVAL OF UNCERTAINTY IS AT MOST XTOL.
+
+        STPMIN AND STPMAX ARE NONNEGATIVE INPUT VARIABLES WHICH SPECIFY LOWER  AND
+        UPPER BOUNDS FOR THE STEP.
+
+        MAXFEV IS A POSITIVE INTEGER INPUT VARIABLE. TERMINATION OCCURS WHEN THE
+        NUMBER OF CALLS TO FCN IS AT LEAST MAXFEV BY THE END OF AN ITERATION.
+
+        INFO IS AN INTEGER OUTPUT VARIABLE SET AS FOLLOWS:
+            INFO = 0  IMPROPER INPUT PARAMETERS.
+
+            INFO = 1  THE SUFFICIENT DECREASE CONDITION AND THE
+                      DIRECTIONAL DERIVATIVE CONDITION HOLD.
+
+            INFO = 2  RELATIVE WIDTH OF THE INTERVAL OF UNCERTAINTY
+                      IS AT MOST XTOL.
+
+            INFO = 3  NUMBER OF CALLS TO FCN HAS REACHED MAXFEV.
+
+            INFO = 4  THE STEP IS AT THE LOWER BOUND STPMIN.
+
+            INFO = 5  THE STEP IS AT THE UPPER BOUND STPMAX.
+
+            INFO = 6  ROUNDING ERRORS PREVENT FURTHER PROGRESS.
+                      THERE MAY NOT BE A STEP WHICH SATISFIES THE
+                      SUFFICIENT DECREASE AND CURVATURE CONDITIONS.
+                      TOLERANCES MAY BE TOO SMALL.
+
+        NFEV IS AN INTEGER OUTPUT VARIABLE SET TO THE NUMBER OF CALLS TO FCN.
+
+        WA IS A WORK ARRAY OF LENGTH N.
+
+        ARGONNE NATIONAL LABORATORY. MINPACK PROJECT. JUNE 1983
+        JORGE J. MORE', DAVID J. THUENTE
+        *************************************************************************/
+        public static void mcsrch(int n,
+            ref double[] x,
+            ref double f,
+            ref double[] g,
+            double[] s,
+            ref double stp,
+            double stpmax,
+            double gtol,
+            ref int info,
+            ref int nfev,
+            ref double[] wa,
+            linminstate state,
+            ref int stage)
+        {
+            double v = 0;
+            double p5 = 0;
+            double p66 = 0;
+            double zero = 0;
+            int i_ = 0;
+
+            
+            //
+            // init
+            //
+            p5 = 0.5;
+            p66 = 0.66;
+            state.xtrapf = 4.0;
+            zero = 0;
+            if( (double)(stpmax)==(double)(0) )
+            {
+                stpmax = defstpmax;
+            }
+            if( (double)(stp)<(double)(stpmin) )
+            {
+                stp = stpmin;
+            }
+            if( (double)(stp)>(double)(stpmax) )
+            {
+                stp = stpmax;
+            }
+            
+            //
+            // Main cycle
+            //
+            while( true )
+            {
+                if( stage==0 )
+                {
+                    
+                    //
+                    // NEXT
+                    //
+                    stage = 2;
+                    continue;
+                }
+                if( stage==2 )
+                {
+                    state.infoc = 1;
+                    info = 0;
+                    
+                    //
+                    //     CHECK THE INPUT PARAMETERS FOR ERRORS.
+                    //
+                    if( (double)(stpmax)<(double)(stpmin) && (double)(stpmax)>(double)(0) )
+                    {
+                        info = 5;
+                        stp = 0.0;
+                        return;
+                    }
+                    if( ((((((n<=0 || (double)(stp)<=(double)(0)) || (double)(ftol)<(double)(0)) || (double)(gtol)<(double)(zero)) || (double)(xtol)<(double)(zero)) || (double)(stpmin)<(double)(zero)) || (double)(stpmax)<(double)(stpmin)) || maxfev<=0 )
+                    {
+                        stage = 0;
+                        return;
+                    }
+                    
+                    //
+                    //     COMPUTE THE INITIAL GRADIENT IN THE SEARCH DIRECTION
+                    //     AND CHECK THAT S IS A DESCENT DIRECTION.
+                    //
+                    v = 0.0;
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        v += g[i_]*s[i_];
+                    }
+                    state.dginit = v;
+                    if( (double)(state.dginit)>=(double)(0) )
+                    {
+                        stage = 0;
+                        return;
+                    }
+                    
+                    //
+                    //     INITIALIZE LOCAL VARIABLES.
+                    //
+                    state.brackt = false;
+                    state.stage1 = true;
+                    nfev = 0;
+                    state.finit = f;
+                    state.dgtest = ftol*state.dginit;
+                    state.width = stpmax-stpmin;
+                    state.width1 = state.width/p5;
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        wa[i_] = x[i_];
+                    }
+                    
+                    //
+                    //     THE VARIABLES STX, FX, DGX CONTAIN THE VALUES OF THE STEP,
+                    //     FUNCTION, AND DIRECTIONAL DERIVATIVE AT THE BEST STEP.
+                    //     THE VARIABLES STY, FY, DGY CONTAIN THE VALUE OF THE STEP,
+                    //     FUNCTION, AND DERIVATIVE AT THE OTHER ENDPOINT OF
+                    //     THE INTERVAL OF UNCERTAINTY.
+                    //     THE VARIABLES STP, F, DG CONTAIN THE VALUES OF THE STEP,
+                    //     FUNCTION, AND DERIVATIVE AT THE CURRENT STEP.
+                    //
+                    state.stx = 0;
+                    state.fx = state.finit;
+                    state.dgx = state.dginit;
+                    state.sty = 0;
+                    state.fy = state.finit;
+                    state.dgy = state.dginit;
+                    
+                    //
+                    // NEXT
+                    //
+                    stage = 3;
+                    continue;
+                }
+                if( stage==3 )
+                {
+                    
+                    //
+                    //     START OF ITERATION.
+                    //
+                    //     SET THE MINIMUM AND MAXIMUM STEPS TO CORRESPOND
+                    //     TO THE PRESENT INTERVAL OF UNCERTAINTY.
+                    //
+                    if( state.brackt )
+                    {
+                        if( (double)(state.stx)<(double)(state.sty) )
+                        {
+                            state.stmin = state.stx;
+                            state.stmax = state.sty;
+                        }
+                        else
+                        {
+                            state.stmin = state.sty;
+                            state.stmax = state.stx;
+                        }
+                    }
+                    else
+                    {
+                        state.stmin = state.stx;
+                        state.stmax = stp+state.xtrapf*(stp-state.stx);
+                    }
+                    
+                    //
+                    //        FORCE THE STEP TO BE WITHIN THE BOUNDS STPMAX AND STPMIN.
+                    //
+                    if( (double)(stp)>(double)(stpmax) )
+                    {
+                        stp = stpmax;
+                    }
+                    if( (double)(stp)<(double)(stpmin) )
+                    {
+                        stp = stpmin;
+                    }
+                    
+                    //
+                    //        IF AN UNUSUAL TERMINATION IS TO OCCUR THEN LET
+                    //        STP BE THE LOWEST POINT OBTAINED SO FAR.
+                    //
+                    if( (((state.brackt && ((double)(stp)<=(double)(state.stmin) || (double)(stp)>=(double)(state.stmax))) || nfev>=maxfev-1) || state.infoc==0) || (state.brackt && (double)(state.stmax-state.stmin)<=(double)(xtol*state.stmax)) )
+                    {
+                        stp = state.stx;
+                    }
+                    
+                    //
+                    //        EVALUATE THE FUNCTION AND GRADIENT AT STP
+                    //        AND COMPUTE THE DIRECTIONAL DERIVATIVE.
+                    //
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        x[i_] = wa[i_];
+                    }
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        x[i_] = x[i_] + stp*s[i_];
+                    }
+                    
+                    //
+                    // NEXT
+                    //
+                    stage = 4;
+                    return;
+                }
+                if( stage==4 )
+                {
+                    info = 0;
+                    nfev = nfev+1;
+                    v = 0.0;
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        v += g[i_]*s[i_];
+                    }
+                    state.dg = v;
+                    state.ftest1 = state.finit+stp*state.dgtest;
+                    
+                    //
+                    //        TEST FOR CONVERGENCE.
+                    //
+                    if( (state.brackt && ((double)(stp)<=(double)(state.stmin) || (double)(stp)>=(double)(state.stmax))) || state.infoc==0 )
+                    {
+                        info = 6;
+                    }
+                    if( ((double)(stp)==(double)(stpmax) && (double)(f)<=(double)(state.ftest1)) && (double)(state.dg)<=(double)(state.dgtest) )
+                    {
+                        info = 5;
+                    }
+                    if( (double)(stp)==(double)(stpmin) && ((double)(f)>(double)(state.ftest1) || (double)(state.dg)>=(double)(state.dgtest)) )
+                    {
+                        info = 4;
+                    }
+                    if( nfev>=maxfev )
+                    {
+                        info = 3;
+                    }
+                    if( state.brackt && (double)(state.stmax-state.stmin)<=(double)(xtol*state.stmax) )
+                    {
+                        info = 2;
+                    }
+                    if( (double)(f)<=(double)(state.ftest1) && (double)(Math.Abs(state.dg))<=(double)(-(gtol*state.dginit)) )
+                    {
+                        info = 1;
+                    }
+                    
+                    //
+                    //        CHECK FOR TERMINATION.
+                    //
+                    if( info!=0 )
+                    {
+                        stage = 0;
+                        return;
+                    }
+                    
+                    //
+                    //        IN THE FIRST STAGE WE SEEK A STEP FOR WHICH THE MODIFIED
+                    //        FUNCTION HAS A NONPOSITIVE VALUE AND NONNEGATIVE DERIVATIVE.
+                    //
+                    if( (state.stage1 && (double)(f)<=(double)(state.ftest1)) && (double)(state.dg)>=(double)(Math.Min(ftol, gtol)*state.dginit) )
+                    {
+                        state.stage1 = false;
+                    }
+                    
+                    //
+                    //        A MODIFIED FUNCTION IS USED TO PREDICT THE STEP ONLY IF
+                    //        WE HAVE NOT OBTAINED A STEP FOR WHICH THE MODIFIED
+                    //        FUNCTION HAS A NONPOSITIVE FUNCTION VALUE AND NONNEGATIVE
+                    //        DERIVATIVE, AND IF A LOWER FUNCTION VALUE HAS BEEN
+                    //        OBTAINED BUT THE DECREASE IS NOT SUFFICIENT.
+                    //
+                    if( (state.stage1 && (double)(f)<=(double)(state.fx)) && (double)(f)>(double)(state.ftest1) )
+                    {
+                        
+                        //
+                        //           DEFINE THE MODIFIED FUNCTION AND DERIVATIVE VALUES.
+                        //
+                        state.fm = f-stp*state.dgtest;
+                        state.fxm = state.fx-state.stx*state.dgtest;
+                        state.fym = state.fy-state.sty*state.dgtest;
+                        state.dgm = state.dg-state.dgtest;
+                        state.dgxm = state.dgx-state.dgtest;
+                        state.dgym = state.dgy-state.dgtest;
+                        
+                        //
+                        //           CALL CSTEP TO UPDATE THE INTERVAL OF UNCERTAINTY
+                        //           AND TO COMPUTE THE NEW STEP.
+                        //
+                        mcstep(ref state.stx, ref state.fxm, ref state.dgxm, ref state.sty, ref state.fym, ref state.dgym, ref stp, state.fm, state.dgm, ref state.brackt, state.stmin, state.stmax, ref state.infoc);
+                        
+                        //
+                        //           RESET THE FUNCTION AND GRADIENT VALUES FOR F.
+                        //
+                        state.fx = state.fxm+state.stx*state.dgtest;
+                        state.fy = state.fym+state.sty*state.dgtest;
+                        state.dgx = state.dgxm+state.dgtest;
+                        state.dgy = state.dgym+state.dgtest;
+                    }
+                    else
+                    {
+                        
+                        //
+                        //           CALL MCSTEP TO UPDATE THE INTERVAL OF UNCERTAINTY
+                        //           AND TO COMPUTE THE NEW STEP.
+                        //
+                        mcstep(ref state.stx, ref state.fx, ref state.dgx, ref state.sty, ref state.fy, ref state.dgy, ref stp, f, state.dg, ref state.brackt, state.stmin, state.stmax, ref state.infoc);
+                    }
+                    
+                    //
+                    //        FORCE A SUFFICIENT DECREASE IN THE SIZE OF THE
+                    //        INTERVAL OF UNCERTAINTY.
+                    //
+                    if( state.brackt )
+                    {
+                        if( (double)(Math.Abs(state.sty-state.stx))>=(double)(p66*state.width1) )
+                        {
+                            stp = state.stx+p5*(state.sty-state.stx);
+                        }
+                        state.width1 = state.width;
+                        state.width = Math.Abs(state.sty-state.stx);
+                    }
+                    
+                    //
+                    //  NEXT.
+                    //
+                    stage = 3;
+                    continue;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        These functions perform Armijo line search using  at  most  FMAX  function
+        evaluations.  It  doesn't  enforce  some  kind  of  " sufficient decrease"
+        criterion - it just tries different Armijo steps and returns optimum found
+        so far.
+
+        Optimization is done using F-rcomm interface:
+        * ArmijoCreate initializes State structure
+          (reusing previously allocated buffers)
+        * ArmijoIteration is subsequently called
+        * ArmijoResults returns results
+
+        INPUT PARAMETERS:
+            N       -   problem size
+            X       -   array[N], starting point
+            F       -   F(X+S*STP)
+            S       -   step direction, S>0
+            STP     -   step length
+            STPMAX  -   maximum value for STP or zero (if no limit is imposed)
+            FMAX    -   maximum number of function evaluations
+            State   -   optimization state
+
+          -- ALGLIB --
+             Copyright 05.10.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void armijocreate(int n,
+            double[] x,
+            double f,
+            double[] s,
+            double stp,
+            double stpmax,
+            int fmax,
+            armijostate state)
+        {
+            int i_ = 0;
+
+            if( alglib.ap.len(state.x)<n )
+            {
+                state.x = new double[n];
+            }
+            if( alglib.ap.len(state.xbase)<n )
+            {
+                state.xbase = new double[n];
+            }
+            if( alglib.ap.len(state.s)<n )
+            {
+                state.s = new double[n];
+            }
+            state.stpmax = stpmax;
+            state.fmax = fmax;
+            state.stplen = stp;
+            state.fcur = f;
+            state.n = n;
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.xbase[i_] = x[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.s[i_] = s[i_];
+            }
+            state.rstate.ia = new int[0+1];
+            state.rstate.ra = new double[0+1];
+            state.rstate.stage = -1;
+        }
+
+
+        /*************************************************************************
+        This is rcomm-based search function
+
+          -- ALGLIB --
+             Copyright 05.10.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static bool armijoiteration(armijostate state)
+        {
+            bool result = new bool();
+            double v = 0;
+            int n = 0;
+            int i_ = 0;
+
+            
+            //
+            // Reverse communication preparations
+            // I know it looks ugly, but it works the same way
+            // anywhere from C++ to Python.
+            //
+            // This code initializes locals by:
+            // * random values determined during code
+            //   generation - on first subroutine call
+            // * values from previous call - on subsequent calls
+            //
+            if( state.rstate.stage>=0 )
+            {
+                n = state.rstate.ia[0];
+                v = state.rstate.ra[0];
+            }
+            else
+            {
+                n = -983;
+                v = -989;
+            }
+            if( state.rstate.stage==0 )
+            {
+                goto lbl_0;
+            }
+            if( state.rstate.stage==1 )
+            {
+                goto lbl_1;
+            }
+            if( state.rstate.stage==2 )
+            {
+                goto lbl_2;
+            }
+            if( state.rstate.stage==3 )
+            {
+                goto lbl_3;
+            }
+            
+            //
+            // Routine body
+            //
+            if( ((double)(state.stplen)<=(double)(0) || (double)(state.stpmax)<(double)(0)) || state.fmax<2 )
+            {
+                state.info = 0;
+                result = false;
+                return result;
+            }
+            if( (double)(state.stplen)<=(double)(stpmin) )
+            {
+                state.info = 4;
+                result = false;
+                return result;
+            }
+            n = state.n;
+            state.nfev = 0;
+            
+            //
+            // We always need F
+            //
+            state.needf = true;
+            
+            //
+            // Bound StpLen
+            //
+            if( (double)(state.stplen)>(double)(state.stpmax) && (double)(state.stpmax)!=(double)(0) )
+            {
+                state.stplen = state.stpmax;
+            }
+            
+            //
+            // Increase length
+            //
+            v = state.stplen*armijofactor;
+            if( (double)(v)>(double)(state.stpmax) && (double)(state.stpmax)!=(double)(0) )
+            {
+                v = state.stpmax;
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.xbase[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.x[i_] + v*state.s[i_];
+            }
+            state.rstate.stage = 0;
+            goto lbl_rcomm;
+        lbl_0:
+            state.nfev = state.nfev+1;
+            if( (double)(state.f)>=(double)(state.fcur) )
+            {
+                goto lbl_4;
+            }
+            state.stplen = v;
+            state.fcur = state.f;
+        lbl_6:
+            if( false )
+            {
+                goto lbl_7;
+            }
+            
+            //
+            // test stopping conditions
+            //
+            if( state.nfev>=state.fmax )
+            {
+                state.info = 3;
+                result = false;
+                return result;
+            }
+            if( (double)(state.stplen)>=(double)(state.stpmax) )
+            {
+                state.info = 5;
+                result = false;
+                return result;
+            }
+            
+            //
+            // evaluate F
+            //
+            v = state.stplen*armijofactor;
+            if( (double)(v)>(double)(state.stpmax) && (double)(state.stpmax)!=(double)(0) )
+            {
+                v = state.stpmax;
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.xbase[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.x[i_] + v*state.s[i_];
+            }
+            state.rstate.stage = 1;
+            goto lbl_rcomm;
+        lbl_1:
+            state.nfev = state.nfev+1;
+            
+            //
+            // make decision
+            //
+            if( (double)(state.f)<(double)(state.fcur) )
+            {
+                state.stplen = v;
+                state.fcur = state.f;
+            }
+            else
+            {
+                state.info = 1;
+                result = false;
+                return result;
+            }
+            goto lbl_6;
+        lbl_7:
+        lbl_4:
+            
+            //
+            // Decrease length
+            //
+            v = state.stplen/armijofactor;
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.xbase[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.x[i_] + v*state.s[i_];
+            }
+            state.rstate.stage = 2;
+            goto lbl_rcomm;
+        lbl_2:
+            state.nfev = state.nfev+1;
+            if( (double)(state.f)>=(double)(state.fcur) )
+            {
+                goto lbl_8;
+            }
+            state.stplen = state.stplen/armijofactor;
+            state.fcur = state.f;
+        lbl_10:
+            if( false )
+            {
+                goto lbl_11;
+            }
+            
+            //
+            // test stopping conditions
+            //
+            if( state.nfev>=state.fmax )
+            {
+                state.info = 3;
+                result = false;
+                return result;
+            }
+            if( (double)(state.stplen)<=(double)(stpmin) )
+            {
+                state.info = 4;
+                result = false;
+                return result;
+            }
+            
+            //
+            // evaluate F
+            //
+            v = state.stplen/armijofactor;
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.xbase[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.x[i_] + v*state.s[i_];
+            }
+            state.rstate.stage = 3;
+            goto lbl_rcomm;
+        lbl_3:
+            state.nfev = state.nfev+1;
+            
+            //
+            // make decision
+            //
+            if( (double)(state.f)<(double)(state.fcur) )
+            {
+                state.stplen = state.stplen/armijofactor;
+                state.fcur = state.f;
+            }
+            else
+            {
+                state.info = 1;
+                result = false;
+                return result;
+            }
+            goto lbl_10;
+        lbl_11:
+        lbl_8:
+            
+            //
+            // Nothing to be done
+            //
+            state.info = 1;
+            result = false;
+            return result;
+            
+            //
+            // Saving state
+            //
+        lbl_rcomm:
+            result = true;
+            state.rstate.ia[0] = n;
+            state.rstate.ra[0] = v;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Results of Armijo search
+
+        OUTPUT PARAMETERS:
+            INFO    -   on output it is set to one of the return codes:
+                        * 0     improper input params
+                        * 1     optimum step is found with at most FMAX evaluations
+                        * 3     FMAX evaluations were used,
+                                X contains optimum found so far
+                        * 4     step is at lower bound STPMIN
+                        * 5     step is at upper bound
+            STP     -   step length (in case of failure it is still returned)
+            F       -   function value (in case of failure it is still returned)
+
+          -- ALGLIB --
+             Copyright 05.10.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void armijoresults(armijostate state,
+            ref int info,
+            ref double stp,
+            ref double f)
+        {
+            info = state.info;
+            stp = state.stplen;
+            f = state.fcur;
+        }
+
+
+        private static void mcstep(ref double stx,
+            ref double fx,
+            ref double dx,
+            ref double sty,
+            ref double fy,
+            ref double dy,
+            ref double stp,
+            double fp,
+            double dp,
+            ref bool brackt,
+            double stmin,
+            double stmax,
+            ref int info)
+        {
+            bool bound = new bool();
+            double gamma = 0;
+            double p = 0;
+            double q = 0;
+            double r = 0;
+            double s = 0;
+            double sgnd = 0;
+            double stpc = 0;
+            double stpf = 0;
+            double stpq = 0;
+            double theta = 0;
+
+            info = 0;
+            
+            //
+            //     CHECK THE INPUT PARAMETERS FOR ERRORS.
+            //
+            if( ((brackt && ((double)(stp)<=(double)(Math.Min(stx, sty)) || (double)(stp)>=(double)(Math.Max(stx, sty)))) || (double)(dx*(stp-stx))>=(double)(0)) || (double)(stmax)<(double)(stmin) )
+            {
+                return;
+            }
+            
+            //
+            //     DETERMINE IF THE DERIVATIVES HAVE OPPOSITE SIGN.
+            //
+            sgnd = dp*(dx/Math.Abs(dx));
+            
+            //
+            //     FIRST CASE. A HIGHER FUNCTION VALUE.
+            //     THE MINIMUM IS BRACKETED. IF THE CUBIC STEP IS CLOSER
+            //     TO STX THAN THE QUADRATIC STEP, THE CUBIC STEP IS TAKEN,
+            //     ELSE THE AVERAGE OF THE CUBIC AND QUADRATIC STEPS IS TAKEN.
+            //
+            if( (double)(fp)>(double)(fx) )
+            {
+                info = 1;
+                bound = true;
+                theta = 3*(fx-fp)/(stp-stx)+dx+dp;
+                s = Math.Max(Math.Abs(theta), Math.Max(Math.Abs(dx), Math.Abs(dp)));
+                gamma = s*Math.Sqrt(math.sqr(theta/s)-dx/s*(dp/s));
+                if( (double)(stp)<(double)(stx) )
+                {
+                    gamma = -gamma;
+                }
+                p = gamma-dx+theta;
+                q = gamma-dx+gamma+dp;
+                r = p/q;
+                stpc = stx+r*(stp-stx);
+                stpq = stx+dx/((fx-fp)/(stp-stx)+dx)/2*(stp-stx);
+                if( (double)(Math.Abs(stpc-stx))<(double)(Math.Abs(stpq-stx)) )
+                {
+                    stpf = stpc;
+                }
+                else
+                {
+                    stpf = stpc+(stpq-stpc)/2;
+                }
+                brackt = true;
+            }
+            else
+            {
+                if( (double)(sgnd)<(double)(0) )
+                {
+                    
+                    //
+                    //     SECOND CASE. A LOWER FUNCTION VALUE AND DERIVATIVES OF
+                    //     OPPOSITE SIGN. THE MINIMUM IS BRACKETED. IF THE CUBIC
+                    //     STEP IS CLOSER TO STX THAN THE QUADRATIC (SECANT) STEP,
+                    //     THE CUBIC STEP IS TAKEN, ELSE THE QUADRATIC STEP IS TAKEN.
+                    //
+                    info = 2;
+                    bound = false;
+                    theta = 3*(fx-fp)/(stp-stx)+dx+dp;
+                    s = Math.Max(Math.Abs(theta), Math.Max(Math.Abs(dx), Math.Abs(dp)));
+                    gamma = s*Math.Sqrt(math.sqr(theta/s)-dx/s*(dp/s));
+                    if( (double)(stp)>(double)(stx) )
+                    {
+                        gamma = -gamma;
+                    }
+                    p = gamma-dp+theta;
+                    q = gamma-dp+gamma+dx;
+                    r = p/q;
+                    stpc = stp+r*(stx-stp);
+                    stpq = stp+dp/(dp-dx)*(stx-stp);
+                    if( (double)(Math.Abs(stpc-stp))>(double)(Math.Abs(stpq-stp)) )
+                    {
+                        stpf = stpc;
+                    }
+                    else
+                    {
+                        stpf = stpq;
+                    }
+                    brackt = true;
+                }
+                else
+                {
+                    if( (double)(Math.Abs(dp))<(double)(Math.Abs(dx)) )
+                    {
+                        
+                        //
+                        //     THIRD CASE. A LOWER FUNCTION VALUE, DERIVATIVES OF THE
+                        //     SAME SIGN, AND THE MAGNITUDE OF THE DERIVATIVE DECREASES.
+                        //     THE CUBIC STEP IS ONLY USED IF THE CUBIC TENDS TO INFINITY
+                        //     IN THE DIRECTION OF THE STEP OR IF THE MINIMUM OF THE CUBIC
+                        //     IS BEYOND STP. OTHERWISE THE CUBIC STEP IS DEFINED TO BE
+                        //     EITHER STPMIN OR STPMAX. THE QUADRATIC (SECANT) STEP IS ALSO
+                        //     COMPUTED AND IF THE MINIMUM IS BRACKETED THEN THE THE STEP
+                        //     CLOSEST TO STX IS TAKEN, ELSE THE STEP FARTHEST AWAY IS TAKEN.
+                        //
+                        info = 3;
+                        bound = true;
+                        theta = 3*(fx-fp)/(stp-stx)+dx+dp;
+                        s = Math.Max(Math.Abs(theta), Math.Max(Math.Abs(dx), Math.Abs(dp)));
+                        
+                        //
+                        //        THE CASE GAMMA = 0 ONLY ARISES IF THE CUBIC DOES NOT TEND
+                        //        TO INFINITY IN THE DIRECTION OF THE STEP.
+                        //
+                        gamma = s*Math.Sqrt(Math.Max(0, math.sqr(theta/s)-dx/s*(dp/s)));
+                        if( (double)(stp)>(double)(stx) )
+                        {
+                            gamma = -gamma;
+                        }
+                        p = gamma-dp+theta;
+                        q = gamma+(dx-dp)+gamma;
+                        r = p/q;
+                        if( (double)(r)<(double)(0) && (double)(gamma)!=(double)(0) )
+                        {
+                            stpc = stp+r*(stx-stp);
+                        }
+                        else
+                        {
+                            if( (double)(stp)>(double)(stx) )
+                            {
+                                stpc = stmax;
+                            }
+                            else
+                            {
+                                stpc = stmin;
+                            }
+                        }
+                        stpq = stp+dp/(dp-dx)*(stx-stp);
+                        if( brackt )
+                        {
+                            if( (double)(Math.Abs(stp-stpc))<(double)(Math.Abs(stp-stpq)) )
+                            {
+                                stpf = stpc;
+                            }
+                            else
+                            {
+                                stpf = stpq;
+                            }
+                        }
+                        else
+                        {
+                            if( (double)(Math.Abs(stp-stpc))>(double)(Math.Abs(stp-stpq)) )
+                            {
+                                stpf = stpc;
+                            }
+                            else
+                            {
+                                stpf = stpq;
+                            }
+                        }
+                    }
+                    else
+                    {
+                        
+                        //
+                        //     FOURTH CASE. A LOWER FUNCTION VALUE, DERIVATIVES OF THE
+                        //     SAME SIGN, AND THE MAGNITUDE OF THE DERIVATIVE DOES
+                        //     NOT DECREASE. IF THE MINIMUM IS NOT BRACKETED, THE STEP
+                        //     IS EITHER STPMIN OR STPMAX, ELSE THE CUBIC STEP IS TAKEN.
+                        //
+                        info = 4;
+                        bound = false;
+                        if( brackt )
+                        {
+                            theta = 3*(fp-fy)/(sty-stp)+dy+dp;
+                            s = Math.Max(Math.Abs(theta), Math.Max(Math.Abs(dy), Math.Abs(dp)));
+                            gamma = s*Math.Sqrt(math.sqr(theta/s)-dy/s*(dp/s));
+                            if( (double)(stp)>(double)(sty) )
+                            {
+                                gamma = -gamma;
+                            }
+                            p = gamma-dp+theta;
+                            q = gamma-dp+gamma+dy;
+                            r = p/q;
+                            stpc = stp+r*(sty-stp);
+                            stpf = stpc;
+                        }
+                        else
+                        {
+                            if( (double)(stp)>(double)(stx) )
+                            {
+                                stpf = stmax;
+                            }
+                            else
+                            {
+                                stpf = stmin;
+                            }
+                        }
+                    }
+                }
+            }
+            
+            //
+            //     UPDATE THE INTERVAL OF UNCERTAINTY. THIS UPDATE DOES NOT
+            //     DEPEND ON THE NEW STEP OR THE CASE ANALYSIS ABOVE.
+            //
+            if( (double)(fp)>(double)(fx) )
+            {
+                sty = stp;
+                fy = fp;
+                dy = dp;
+            }
+            else
+            {
+                if( (double)(sgnd)<(double)(0.0) )
+                {
+                    sty = stx;
+                    fy = fx;
+                    dy = dx;
+                }
+                stx = stp;
+                fx = fp;
+                dx = dp;
+            }
+            
+            //
+            //     COMPUTE THE NEW STEP AND SAFEGUARD IT.
+            //
+            stpf = Math.Min(stmax, stpf);
+            stpf = Math.Max(stmin, stpf);
+            stp = stpf;
+            if( brackt && bound )
+            {
+                if( (double)(sty)>(double)(stx) )
+                {
+                    stp = Math.Min(stx+0.66*(sty-stx), stp);
+                }
+                else
+                {
+                    stp = Math.Max(stx+0.66*(sty-stx), stp);
+                }
+            }
+        }
+
+
+    }
+    public class ftbase
+    {
+        public class ftplan
+        {
+            public int[] plan;
+            public double[] precomputed;
+            public double[] tmpbuf;
+            public double[] stackbuf;
+            public ftplan()
+            {
+                plan = new int[0];
+                precomputed = new double[0];
+                tmpbuf = new double[0];
+                stackbuf = new double[0];
+            }
+        };
+
+
+
+
+        public const int ftbaseplanentrysize = 8;
+        public const int ftbasecffttask = 0;
+        public const int ftbaserfhttask = 1;
+        public const int ftbaserffttask = 2;
+        public const int fftcooleytukeyplan = 0;
+        public const int fftbluesteinplan = 1;
+        public const int fftcodeletplan = 2;
+        public const int fhtcooleytukeyplan = 3;
+        public const int fhtcodeletplan = 4;
+        public const int fftrealcooleytukeyplan = 5;
+        public const int fftemptyplan = 6;
+        public const int fhtn2plan = 999;
+        public const int ftbaseupdatetw = 4;
+        public const int ftbasecodeletrecommended = 5;
+        public const double ftbaseinefficiencyfactor = 1.3;
+        public const int ftbasemaxsmoothfactor = 5;
+
+
+        /*************************************************************************
+        This subroutine generates FFT plan - a decomposition of a N-length FFT to
+        the more simpler operations. Plan consists of the root entry and the child
+        entries.
+
+        Subroutine parameters:
+            N               task size
+            
+        Output parameters:
+            Plan            plan
+
+          -- ALGLIB --
+             Copyright 01.05.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void ftbasegeneratecomplexfftplan(int n,
+            ftplan plan)
+        {
+            int planarraysize = 0;
+            int plansize = 0;
+            int precomputedsize = 0;
+            int tmpmemsize = 0;
+            int stackmemsize = 0;
+            int stackptr = 0;
+
+            planarraysize = 1;
+            plansize = 0;
+            precomputedsize = 0;
+            stackmemsize = 0;
+            stackptr = 0;
+            tmpmemsize = 2*n;
+            plan.plan = new int[planarraysize];
+            ftbasegenerateplanrec(n, ftbasecffttask, plan, ref plansize, ref precomputedsize, ref planarraysize, ref tmpmemsize, ref stackmemsize, stackptr);
+            alglib.ap.assert(stackptr==0, "Internal error in FTBaseGenerateComplexFFTPlan: stack ptr!");
+            plan.stackbuf = new double[Math.Max(stackmemsize, 1)];
+            plan.tmpbuf = new double[Math.Max(tmpmemsize, 1)];
+            plan.precomputed = new double[Math.Max(precomputedsize, 1)];
+            stackptr = 0;
+            ftbaseprecomputeplanrec(plan, 0, stackptr);
+            alglib.ap.assert(stackptr==0, "Internal error in FTBaseGenerateComplexFFTPlan: stack ptr!");
+        }
+
+
+        /*************************************************************************
+        Generates real FFT plan
+        *************************************************************************/
+        public static void ftbasegeneraterealfftplan(int n,
+            ftplan plan)
+        {
+            int planarraysize = 0;
+            int plansize = 0;
+            int precomputedsize = 0;
+            int tmpmemsize = 0;
+            int stackmemsize = 0;
+            int stackptr = 0;
+
+            planarraysize = 1;
+            plansize = 0;
+            precomputedsize = 0;
+            stackmemsize = 0;
+            stackptr = 0;
+            tmpmemsize = 2*n;
+            plan.plan = new int[planarraysize];
+            ftbasegenerateplanrec(n, ftbaserffttask, plan, ref plansize, ref precomputedsize, ref planarraysize, ref tmpmemsize, ref stackmemsize, stackptr);
+            alglib.ap.assert(stackptr==0, "Internal error in FTBaseGenerateRealFFTPlan: stack ptr!");
+            plan.stackbuf = new double[Math.Max(stackmemsize, 1)];
+            plan.tmpbuf = new double[Math.Max(tmpmemsize, 1)];
+            plan.precomputed = new double[Math.Max(precomputedsize, 1)];
+            stackptr = 0;
+            ftbaseprecomputeplanrec(plan, 0, stackptr);
+            alglib.ap.assert(stackptr==0, "Internal error in FTBaseGenerateRealFFTPlan: stack ptr!");
+        }
+
+
+        /*************************************************************************
+        Generates real FHT plan
+        *************************************************************************/
+        public static void ftbasegeneraterealfhtplan(int n,
+            ftplan plan)
+        {
+            int planarraysize = 0;
+            int plansize = 0;
+            int precomputedsize = 0;
+            int tmpmemsize = 0;
+            int stackmemsize = 0;
+            int stackptr = 0;
+
+            planarraysize = 1;
+            plansize = 0;
+            precomputedsize = 0;
+            stackmemsize = 0;
+            stackptr = 0;
+            tmpmemsize = n;
+            plan.plan = new int[planarraysize];
+            ftbasegenerateplanrec(n, ftbaserfhttask, plan, ref plansize, ref precomputedsize, ref planarraysize, ref tmpmemsize, ref stackmemsize, stackptr);
+            alglib.ap.assert(stackptr==0, "Internal error in FTBaseGenerateRealFHTPlan: stack ptr!");
+            plan.stackbuf = new double[Math.Max(stackmemsize, 1)];
+            plan.tmpbuf = new double[Math.Max(tmpmemsize, 1)];
+            plan.precomputed = new double[Math.Max(precomputedsize, 1)];
+            stackptr = 0;
+            ftbaseprecomputeplanrec(plan, 0, stackptr);
+            alglib.ap.assert(stackptr==0, "Internal error in FTBaseGenerateRealFHTPlan: stack ptr!");
+        }
+
+
+        /*************************************************************************
+        This subroutine executes FFT/FHT plan.
+
+        If Plan is a:
+        * complex FFT plan  -   sizeof(A)=2*N,
+                                A contains interleaved real/imaginary values
+        * real FFT plan     -   sizeof(A)=2*N,
+                                A contains real values interleaved with zeros
+        * real FHT plan     -   sizeof(A)=2*N,
+                                A contains real values interleaved with zeros
+
+          -- ALGLIB --
+             Copyright 01.05.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void ftbaseexecuteplan(ref double[] a,
+            int aoffset,
+            int n,
+            ftplan plan)
+        {
+            int stackptr = 0;
+
+            stackptr = 0;
+            ftbaseexecuteplanrec(ref a, aoffset, plan, 0, stackptr);
+        }
+
+
+        /*************************************************************************
+        Recurrent subroutine for the FTBaseExecutePlan
+
+        Parameters:
+            A           FFT'ed array
+            AOffset     offset of the FFT'ed part (distance is measured in doubles)
+
+          -- ALGLIB --
+             Copyright 01.05.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void ftbaseexecuteplanrec(ref double[] a,
+            int aoffset,
+            ftplan plan,
+            int entryoffset,
+            int stackptr)
+        {
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int n1 = 0;
+            int n2 = 0;
+            int n = 0;
+            int m = 0;
+            int offs = 0;
+            int offs1 = 0;
+            int offs2 = 0;
+            int offsa = 0;
+            int offsb = 0;
+            int offsp = 0;
+            double hk = 0;
+            double hnk = 0;
+            double x = 0;
+            double y = 0;
+            double bx = 0;
+            double by = 0;
+            double[] emptyarray = new double[0];
+            double a0x = 0;
+            double a0y = 0;
+            double a1x = 0;
+            double a1y = 0;
+            double a2x = 0;
+            double a2y = 0;
+            double a3x = 0;
+            double a3y = 0;
+            double v0 = 0;
+            double v1 = 0;
+            double v2 = 0;
+            double v3 = 0;
+            double t1x = 0;
+            double t1y = 0;
+            double t2x = 0;
+            double t2y = 0;
+            double t3x = 0;
+            double t3y = 0;
+            double t4x = 0;
+            double t4y = 0;
+            double t5x = 0;
+            double t5y = 0;
+            double m1x = 0;
+            double m1y = 0;
+            double m2x = 0;
+            double m2y = 0;
+            double m3x = 0;
+            double m3y = 0;
+            double m4x = 0;
+            double m4y = 0;
+            double m5x = 0;
+            double m5y = 0;
+            double s1x = 0;
+            double s1y = 0;
+            double s2x = 0;
+            double s2y = 0;
+            double s3x = 0;
+            double s3y = 0;
+            double s4x = 0;
+            double s4y = 0;
+            double s5x = 0;
+            double s5y = 0;
+            double c1 = 0;
+            double c2 = 0;
+            double c3 = 0;
+            double c4 = 0;
+            double c5 = 0;
+            double[] tmp = new double[0];
+            int i_ = 0;
+            int i1_ = 0;
+
+            if( plan.plan[entryoffset+3]==fftemptyplan )
+            {
+                return;
+            }
+            if( plan.plan[entryoffset+3]==fftcooleytukeyplan )
+            {
+                
+                //
+                // Cooley-Tukey plan
+                // * transposition
+                // * row-wise FFT
+                // * twiddle factors:
+                //   - TwBase is a basis twiddle factor for I=1, J=1
+                //   - TwRow is a twiddle factor for a second element in a row (J=1)
+                //   - Tw is a twiddle factor for a current element
+                // * transposition again
+                // * row-wise FFT again
+                //
+                n1 = plan.plan[entryoffset+1];
+                n2 = plan.plan[entryoffset+2];
+                internalcomplexlintranspose(ref a, n1, n2, aoffset, ref plan.tmpbuf);
+                for(i=0; i<=n2-1; i++)
+                {
+                    ftbaseexecuteplanrec(ref a, aoffset+i*n1*2, plan, plan.plan[entryoffset+5], stackptr);
+                }
+                ffttwcalc(ref a, aoffset, n1, n2);
+                internalcomplexlintranspose(ref a, n2, n1, aoffset, ref plan.tmpbuf);
+                for(i=0; i<=n1-1; i++)
+                {
+                    ftbaseexecuteplanrec(ref a, aoffset+i*n2*2, plan, plan.plan[entryoffset+6], stackptr);
+                }
+                internalcomplexlintranspose(ref a, n1, n2, aoffset, ref plan.tmpbuf);
+                return;
+            }
+            if( plan.plan[entryoffset+3]==fftrealcooleytukeyplan )
+            {
+                
+                //
+                // Cooley-Tukey plan
+                // * transposition
+                // * row-wise FFT
+                // * twiddle factors:
+                //   - TwBase is a basis twiddle factor for I=1, J=1
+                //   - TwRow is a twiddle factor for a second element in a row (J=1)
+                //   - Tw is a twiddle factor for a current element
+                // * transposition again
+                // * row-wise FFT again
+                //
+                n1 = plan.plan[entryoffset+1];
+                n2 = plan.plan[entryoffset+2];
+                internalcomplexlintranspose(ref a, n2, n1, aoffset, ref plan.tmpbuf);
+                for(i=0; i<=n1/2-1; i++)
+                {
+                    
+                    //
+                    // pack two adjacent smaller real FFT's together,
+                    // make one complex FFT,
+                    // unpack result
+                    //
+                    offs = aoffset+2*i*n2*2;
+                    for(k=0; k<=n2-1; k++)
+                    {
+                        a[offs+2*k+1] = a[offs+2*n2+2*k+0];
+                    }
+                    ftbaseexecuteplanrec(ref a, offs, plan, plan.plan[entryoffset+6], stackptr);
+                    plan.tmpbuf[0] = a[offs+0];
+                    plan.tmpbuf[1] = 0;
+                    plan.tmpbuf[2*n2+0] = a[offs+1];
+                    plan.tmpbuf[2*n2+1] = 0;
+                    for(k=1; k<=n2-1; k++)
+                    {
+                        offs1 = 2*k;
+                        offs2 = 2*n2+2*k;
+                        hk = a[offs+2*k+0];
+                        hnk = a[offs+2*(n2-k)+0];
+                        plan.tmpbuf[offs1+0] = 0.5*(hk+hnk);
+                        plan.tmpbuf[offs2+1] = -(0.5*(hk-hnk));
+                        hk = a[offs+2*k+1];
+                        hnk = a[offs+2*(n2-k)+1];
+                        plan.tmpbuf[offs2+0] = 0.5*(hk+hnk);
+                        plan.tmpbuf[offs1+1] = 0.5*(hk-hnk);
+                    }
+                    i1_ = (0) - (offs);
+                    for(i_=offs; i_<=offs+2*n2*2-1;i_++)
+                    {
+                        a[i_] = plan.tmpbuf[i_+i1_];
+                    }
+                }
+                if( n1%2!=0 )
+                {
+                    ftbaseexecuteplanrec(ref a, aoffset+(n1-1)*n2*2, plan, plan.plan[entryoffset+6], stackptr);
+                }
+                ffttwcalc(ref a, aoffset, n2, n1);
+                internalcomplexlintranspose(ref a, n1, n2, aoffset, ref plan.tmpbuf);
+                for(i=0; i<=n2-1; i++)
+                {
+                    ftbaseexecuteplanrec(ref a, aoffset+i*n1*2, plan, plan.plan[entryoffset+5], stackptr);
+                }
+                internalcomplexlintranspose(ref a, n2, n1, aoffset, ref plan.tmpbuf);
+                return;
+            }
+            if( plan.plan[entryoffset+3]==fhtcooleytukeyplan )
+            {
+                
+                //
+                // Cooley-Tukey FHT plan:
+                // * transpose                    \
+                // * smaller FHT's                |
+                // * pre-process                  |
+                // * multiply by twiddle factors  | corresponds to multiplication by H1
+                // * post-process                 |
+                // * transpose again              /
+                // * multiply by H2 (smaller FHT's)
+                // * final transposition
+                //
+                // For more details see Vitezslav Vesely, "Fast algorithms
+                // of Fourier and Hartley transform and their implementation in MATLAB",
+                // page 31.
+                //
+                n1 = plan.plan[entryoffset+1];
+                n2 = plan.plan[entryoffset+2];
+                n = n1*n2;
+                internalreallintranspose(ref a, n1, n2, aoffset, ref plan.tmpbuf);
+                for(i=0; i<=n2-1; i++)
+                {
+                    ftbaseexecuteplanrec(ref a, aoffset+i*n1, plan, plan.plan[entryoffset+5], stackptr);
+                }
+                for(i=0; i<=n2-1; i++)
+                {
+                    for(j=0; j<=n1-1; j++)
+                    {
+                        offsa = aoffset+i*n1;
+                        hk = a[offsa+j];
+                        hnk = a[offsa+(n1-j)%n1];
+                        offs = 2*(i*n1+j);
+                        plan.tmpbuf[offs+0] = -(0.5*(hnk-hk));
+                        plan.tmpbuf[offs+1] = 0.5*(hk+hnk);
+                    }
+                }
+                ffttwcalc(ref plan.tmpbuf, 0, n1, n2);
+                for(j=0; j<=n1-1; j++)
+                {
+                    a[aoffset+j] = plan.tmpbuf[2*j+0]+plan.tmpbuf[2*j+1];
+                }
+                if( n2%2==0 )
+                {
+                    offs = 2*(n2/2)*n1;
+                    offsa = aoffset+n2/2*n1;
+                    for(j=0; j<=n1-1; j++)
+                    {
+                        a[offsa+j] = plan.tmpbuf[offs+2*j+0]+plan.tmpbuf[offs+2*j+1];
+                    }
+                }
+                for(i=1; i<=(n2+1)/2-1; i++)
+                {
+                    offs = 2*i*n1;
+                    offs2 = 2*(n2-i)*n1;
+                    offsa = aoffset+i*n1;
+                    for(j=0; j<=n1-1; j++)
+                    {
+                        a[offsa+j] = plan.tmpbuf[offs+2*j+1]+plan.tmpbuf[offs2+2*j+0];
+                    }
+                    offsa = aoffset+(n2-i)*n1;
+                    for(j=0; j<=n1-1; j++)
+                    {
+                        a[offsa+j] = plan.tmpbuf[offs+2*j+0]+plan.tmpbuf[offs2+2*j+1];
+                    }
+                }
+                internalreallintranspose(ref a, n2, n1, aoffset, ref plan.tmpbuf);
+                for(i=0; i<=n1-1; i++)
+                {
+                    ftbaseexecuteplanrec(ref a, aoffset+i*n2, plan, plan.plan[entryoffset+6], stackptr);
+                }
+                internalreallintranspose(ref a, n1, n2, aoffset, ref plan.tmpbuf);
+                return;
+            }
+            if( plan.plan[entryoffset+3]==fhtn2plan )
+            {
+                
+                //
+                // Cooley-Tukey FHT plan
+                //
+                n1 = plan.plan[entryoffset+1];
+                n2 = plan.plan[entryoffset+2];
+                n = n1*n2;
+                reffht(ref a, n, aoffset);
+                return;
+            }
+            if( plan.plan[entryoffset+3]==fftcodeletplan )
+            {
+                n1 = plan.plan[entryoffset+1];
+                n2 = plan.plan[entryoffset+2];
+                n = n1*n2;
+                if( n==2 )
+                {
+                    a0x = a[aoffset+0];
+                    a0y = a[aoffset+1];
+                    a1x = a[aoffset+2];
+                    a1y = a[aoffset+3];
+                    v0 = a0x+a1x;
+                    v1 = a0y+a1y;
+                    v2 = a0x-a1x;
+                    v3 = a0y-a1y;
+                    a[aoffset+0] = v0;
+                    a[aoffset+1] = v1;
+                    a[aoffset+2] = v2;
+                    a[aoffset+3] = v3;
+                    return;
+                }
+                if( n==3 )
+                {
+                    offs = plan.plan[entryoffset+7];
+                    c1 = plan.precomputed[offs+0];
+                    c2 = plan.precomputed[offs+1];
+                    a0x = a[aoffset+0];
+                    a0y = a[aoffset+1];
+                    a1x = a[aoffset+2];
+                    a1y = a[aoffset+3];
+                    a2x = a[aoffset+4];
+                    a2y = a[aoffset+5];
+                    t1x = a1x+a2x;
+                    t1y = a1y+a2y;
+                    a0x = a0x+t1x;
+                    a0y = a0y+t1y;
+                    m1x = c1*t1x;
+                    m1y = c1*t1y;
+                    m2x = c2*(a1y-a2y);
+                    m2y = c2*(a2x-a1x);
+                    s1x = a0x+m1x;
+                    s1y = a0y+m1y;
+                    a1x = s1x+m2x;
+                    a1y = s1y+m2y;
+                    a2x = s1x-m2x;
+                    a2y = s1y-m2y;
+                    a[aoffset+0] = a0x;
+                    a[aoffset+1] = a0y;
+                    a[aoffset+2] = a1x;
+                    a[aoffset+3] = a1y;
+                    a[aoffset+4] = a2x;
+                    a[aoffset+5] = a2y;
+                    return;
+                }
+                if( n==4 )
+                {
+                    a0x = a[aoffset+0];
+                    a0y = a[aoffset+1];
+                    a1x = a[aoffset+2];
+                    a1y = a[aoffset+3];
+                    a2x = a[aoffset+4];
+                    a2y = a[aoffset+5];
+                    a3x = a[aoffset+6];
+                    a3y = a[aoffset+7];
+                    t1x = a0x+a2x;
+                    t1y = a0y+a2y;
+                    t2x = a1x+a3x;
+                    t2y = a1y+a3y;
+                    m2x = a0x-a2x;
+                    m2y = a0y-a2y;
+                    m3x = a1y-a3y;
+                    m3y = a3x-a1x;
+                    a[aoffset+0] = t1x+t2x;
+                    a[aoffset+1] = t1y+t2y;
+                    a[aoffset+4] = t1x-t2x;
+                    a[aoffset+5] = t1y-t2y;
+                    a[aoffset+2] = m2x+m3x;
+                    a[aoffset+3] = m2y+m3y;
+                    a[aoffset+6] = m2x-m3x;
+                    a[aoffset+7] = m2y-m3y;
+                    return;
+                }
+                if( n==5 )
+                {
+                    offs = plan.plan[entryoffset+7];
+                    c1 = plan.precomputed[offs+0];
+                    c2 = plan.precomputed[offs+1];
+                    c3 = plan.precomputed[offs+2];
+                    c4 = plan.precomputed[offs+3];
+                    c5 = plan.precomputed[offs+4];
+                    t1x = a[aoffset+2]+a[aoffset+8];
+                    t1y = a[aoffset+3]+a[aoffset+9];
+                    t2x = a[aoffset+4]+a[aoffset+6];
+                    t2y = a[aoffset+5]+a[aoffset+7];
+                    t3x = a[aoffset+2]-a[aoffset+8];
+                    t3y = a[aoffset+3]-a[aoffset+9];
+                    t4x = a[aoffset+6]-a[aoffset+4];
+                    t4y = a[aoffset+7]-a[aoffset+5];
+                    t5x = t1x+t2x;
+                    t5y = t1y+t2y;
+                    a[aoffset+0] = a[aoffset+0]+t5x;
+                    a[aoffset+1] = a[aoffset+1]+t5y;
+                    m1x = c1*t5x;
+                    m1y = c1*t5y;
+                    m2x = c2*(t1x-t2x);
+                    m2y = c2*(t1y-t2y);
+                    m3x = -(c3*(t3y+t4y));
+                    m3y = c3*(t3x+t4x);
+                    m4x = -(c4*t4y);
+                    m4y = c4*t4x;
+                    m5x = -(c5*t3y);
+                    m5y = c5*t3x;
+                    s3x = m3x-m4x;
+                    s3y = m3y-m4y;
+                    s5x = m3x+m5x;
+                    s5y = m3y+m5y;
+                    s1x = a[aoffset+0]+m1x;
+                    s1y = a[aoffset+1]+m1y;
+                    s2x = s1x+m2x;
+                    s2y = s1y+m2y;
+                    s4x = s1x-m2x;
+                    s4y = s1y-m2y;
+                    a[aoffset+2] = s2x+s3x;
+                    a[aoffset+3] = s2y+s3y;
+                    a[aoffset+4] = s4x+s5x;
+                    a[aoffset+5] = s4y+s5y;
+                    a[aoffset+6] = s4x-s5x;
+                    a[aoffset+7] = s4y-s5y;
+                    a[aoffset+8] = s2x-s3x;
+                    a[aoffset+9] = s2y-s3y;
+                    return;
+                }
+            }
+            if( plan.plan[entryoffset+3]==fhtcodeletplan )
+            {
+                n1 = plan.plan[entryoffset+1];
+                n2 = plan.plan[entryoffset+2];
+                n = n1*n2;
+                if( n==2 )
+                {
+                    a0x = a[aoffset+0];
+                    a1x = a[aoffset+1];
+                    a[aoffset+0] = a0x+a1x;
+                    a[aoffset+1] = a0x-a1x;
+                    return;
+                }
+                if( n==3 )
+                {
+                    offs = plan.plan[entryoffset+7];
+                    c1 = plan.precomputed[offs+0];
+                    c2 = plan.precomputed[offs+1];
+                    a0x = a[aoffset+0];
+                    a1x = a[aoffset+1];
+                    a2x = a[aoffset+2];
+                    t1x = a1x+a2x;
+                    a0x = a0x+t1x;
+                    m1x = c1*t1x;
+                    m2y = c2*(a2x-a1x);
+                    s1x = a0x+m1x;
+                    a[aoffset+0] = a0x;
+                    a[aoffset+1] = s1x-m2y;
+                    a[aoffset+2] = s1x+m2y;
+                    return;
+                }
+                if( n==4 )
+                {
+                    a0x = a[aoffset+0];
+                    a1x = a[aoffset+1];
+                    a2x = a[aoffset+2];
+                    a3x = a[aoffset+3];
+                    t1x = a0x+a2x;
+                    t2x = a1x+a3x;
+                    m2x = a0x-a2x;
+                    m3y = a3x-a1x;
+                    a[aoffset+0] = t1x+t2x;
+                    a[aoffset+1] = m2x-m3y;
+                    a[aoffset+2] = t1x-t2x;
+                    a[aoffset+3] = m2x+m3y;
+                    return;
+                }
+                if( n==5 )
+                {
+                    offs = plan.plan[entryoffset+7];
+                    c1 = plan.precomputed[offs+0];
+                    c2 = plan.precomputed[offs+1];
+                    c3 = plan.precomputed[offs+2];
+                    c4 = plan.precomputed[offs+3];
+                    c5 = plan.precomputed[offs+4];
+                    t1x = a[aoffset+1]+a[aoffset+4];
+                    t2x = a[aoffset+2]+a[aoffset+3];
+                    t3x = a[aoffset+1]-a[aoffset+4];
+                    t4x = a[aoffset+3]-a[aoffset+2];
+                    t5x = t1x+t2x;
+                    v0 = a[aoffset+0]+t5x;
+                    a[aoffset+0] = v0;
+                    m2x = c2*(t1x-t2x);
+                    m3y = c3*(t3x+t4x);
+                    s3y = m3y-c4*t4x;
+                    s5y = m3y+c5*t3x;
+                    s1x = v0+c1*t5x;
+                    s2x = s1x+m2x;
+                    s4x = s1x-m2x;
+                    a[aoffset+1] = s2x-s3y;
+                    a[aoffset+2] = s4x-s5y;
+                    a[aoffset+3] = s4x+s5y;
+                    a[aoffset+4] = s2x+s3y;
+                    return;
+                }
+            }
+            if( plan.plan[entryoffset+3]==fftbluesteinplan )
+            {
+                
+                //
+                // Bluestein plan:
+                // 1. multiply by precomputed coefficients
+                // 2. make convolution: forward FFT, multiplication by precomputed FFT
+                //    and backward FFT. backward FFT is represented as
+                //
+                //        invfft(x) = fft(x')'/M
+                //
+                //    for performance reasons reduction of inverse FFT to
+                //    forward FFT is merged with multiplication of FFT components
+                //    and last stage of Bluestein's transformation.
+                // 3. post-multiplication by Bluestein factors
+                //
+                n = plan.plan[entryoffset+1];
+                m = plan.plan[entryoffset+4];
+                offs = plan.plan[entryoffset+7];
+                for(i=stackptr+2*n; i<=stackptr+2*m-1; i++)
+                {
+                    plan.stackbuf[i] = 0;
+                }
+                offsp = offs+2*m;
+                offsa = aoffset;
+                offsb = stackptr;
+                for(i=0; i<=n-1; i++)
+                {
+                    bx = plan.precomputed[offsp+0];
+                    by = plan.precomputed[offsp+1];
+                    x = a[offsa+0];
+                    y = a[offsa+1];
+                    plan.stackbuf[offsb+0] = x*bx-y*-by;
+                    plan.stackbuf[offsb+1] = x*-by+y*bx;
+                    offsp = offsp+2;
+                    offsa = offsa+2;
+                    offsb = offsb+2;
+                }
+                ftbaseexecuteplanrec(ref plan.stackbuf, stackptr, plan, plan.plan[entryoffset+5], stackptr+2*2*m);
+                offsb = stackptr;
+                offsp = offs;
+                for(i=0; i<=m-1; i++)
+                {
+                    x = plan.stackbuf[offsb+0];
+                    y = plan.stackbuf[offsb+1];
+                    bx = plan.precomputed[offsp+0];
+                    by = plan.precomputed[offsp+1];
+                    plan.stackbuf[offsb+0] = x*bx-y*by;
+                    plan.stackbuf[offsb+1] = -(x*by+y*bx);
+                    offsb = offsb+2;
+                    offsp = offsp+2;
+                }
+                ftbaseexecuteplanrec(ref plan.stackbuf, stackptr, plan, plan.plan[entryoffset+5], stackptr+2*2*m);
+                offsb = stackptr;
+                offsp = offs+2*m;
+                offsa = aoffset;
+                for(i=0; i<=n-1; i++)
+                {
+                    x = plan.stackbuf[offsb+0]/m;
+                    y = -(plan.stackbuf[offsb+1]/m);
+                    bx = plan.precomputed[offsp+0];
+                    by = plan.precomputed[offsp+1];
+                    a[offsa+0] = x*bx-y*-by;
+                    a[offsa+1] = x*-by+y*bx;
+                    offsp = offsp+2;
+                    offsa = offsa+2;
+                    offsb = offsb+2;
+                }
+                return;
+            }
+        }
+
+
+        /*************************************************************************
+        Returns good factorization N=N1*N2.
+
+        Usually N1<=N2 (but not always - small N's may be exception).
+        if N1<>1 then N2<>1.
+
+        Factorization is chosen depending on task type and codelets we have.
+
+          -- ALGLIB --
+             Copyright 01.05.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void ftbasefactorize(int n,
+            int tasktype,
+            ref int n1,
+            ref int n2)
+        {
+            int j = 0;
+
+            n1 = 0;
+            n2 = 0;
+
+            n1 = 0;
+            n2 = 0;
+            
+            //
+            // try to find good codelet
+            //
+            if( n1*n2!=n )
+            {
+                for(j=ftbasecodeletrecommended; j>=2; j--)
+                {
+                    if( n%j==0 )
+                    {
+                        n1 = j;
+                        n2 = n/j;
+                        break;
+                    }
+                }
+            }
+            
+            //
+            // try to factorize N
+            //
+            if( n1*n2!=n )
+            {
+                for(j=ftbasecodeletrecommended+1; j<=n-1; j++)
+                {
+                    if( n%j==0 )
+                    {
+                        n1 = j;
+                        n2 = n/j;
+                        break;
+                    }
+                }
+            }
+            
+            //
+            // looks like N is prime :(
+            //
+            if( n1*n2!=n )
+            {
+                n1 = 1;
+                n2 = n;
+            }
+            
+            //
+            // normalize
+            //
+            if( n2==1 && n1!=1 )
+            {
+                n2 = n1;
+                n1 = 1;
+            }
+        }
+
+
+        /*************************************************************************
+        Is number smooth?
+
+          -- ALGLIB --
+             Copyright 01.05.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static bool ftbaseissmooth(int n)
+        {
+            bool result = new bool();
+            int i = 0;
+
+            for(i=2; i<=ftbasemaxsmoothfactor; i++)
+            {
+                while( n%i==0 )
+                {
+                    n = n/i;
+                }
+            }
+            result = n==1;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Returns smallest smooth (divisible only by 2, 3, 5) number that is greater
+        than or equal to max(N,2)
+
+          -- ALGLIB --
+             Copyright 01.05.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static int ftbasefindsmooth(int n)
+        {
+            int result = 0;
+            int best = 0;
+
+            best = 2;
+            while( best<n )
+            {
+                best = 2*best;
+            }
+            ftbasefindsmoothrec(n, 1, 2, ref best);
+            result = best;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Returns  smallest  smooth  (divisible only by 2, 3, 5) even number that is
+        greater than or equal to max(N,2)
+
+          -- ALGLIB --
+             Copyright 01.05.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static int ftbasefindsmootheven(int n)
+        {
+            int result = 0;
+            int best = 0;
+
+            best = 2;
+            while( best<n )
+            {
+                best = 2*best;
+            }
+            ftbasefindsmoothrec(n, 2, 2, ref best);
+            result = best;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Returns estimate of FLOP count for the FFT.
+
+        It is only an estimate based on operations count for the PERFECT FFT
+        and relative inefficiency of the algorithm actually used.
+
+        N should be power of 2, estimates are badly wrong for non-power-of-2 N's.
+
+          -- ALGLIB --
+             Copyright 01.05.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static double ftbasegetflopestimate(int n)
+        {
+            double result = 0;
+
+            result = ftbaseinefficiencyfactor*(4*n*Math.Log(n)/Math.Log(2)-6*n+8);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Recurrent subroutine for the FFTGeneratePlan:
+
+        PARAMETERS:
+            N                   plan size
+            IsReal              whether input is real or not.
+                                subroutine MUST NOT ignore this flag because real
+                                inputs comes with non-initialized imaginary parts,
+                                so ignoring this flag will result in corrupted output
+            HalfOut             whether full output or only half of it from 0 to
+                                floor(N/2) is needed. This flag may be ignored if
+                                doing so will simplify calculations
+            Plan                plan array
+            PlanSize            size of used part (in integers)
+            PrecomputedSize     size of precomputed array allocated yet
+            PlanArraySize       plan array size (actual)
+            TmpMemSize          temporary memory required size
+            BluesteinMemSize    temporary memory required size
+
+          -- ALGLIB --
+             Copyright 01.05.2009 by Bochkanov Sergey
+        *************************************************************************/
+        private static void ftbasegenerateplanrec(int n,
+            int tasktype,
+            ftplan plan,
+            ref int plansize,
+            ref int precomputedsize,
+            ref int planarraysize,
+            ref int tmpmemsize,
+            ref int stackmemsize,
+            int stackptr)
+        {
+            int k = 0;
+            int m = 0;
+            int n1 = 0;
+            int n2 = 0;
+            int esize = 0;
+            int entryoffset = 0;
+
+            
+            //
+            // prepare
+            //
+            if( plansize+ftbaseplanentrysize>planarraysize )
+            {
+                fftarrayresize(ref plan.plan, ref planarraysize, 8*planarraysize);
+            }
+            entryoffset = plansize;
+            esize = ftbaseplanentrysize;
+            plansize = plansize+esize;
+            
+            //
+            // if N=1, generate empty plan and exit
+            //
+            if( n==1 )
+            {
+                plan.plan[entryoffset+0] = esize;
+                plan.plan[entryoffset+1] = -1;
+                plan.plan[entryoffset+2] = -1;
+                plan.plan[entryoffset+3] = fftemptyplan;
+                plan.plan[entryoffset+4] = -1;
+                plan.plan[entryoffset+5] = -1;
+                plan.plan[entryoffset+6] = -1;
+                plan.plan[entryoffset+7] = -1;
+                return;
+            }
+            
+            //
+            // generate plans
+            //
+            ftbasefactorize(n, tasktype, ref n1, ref n2);
+            if( tasktype==ftbasecffttask || tasktype==ftbaserffttask )
+            {
+                
+                //
+                // complex FFT plans
+                //
+                if( n1!=1 )
+                {
+                    
+                    //
+                    // Cooley-Tukey plan (real or complex)
+                    //
+                    // Note that child plans are COMPLEX
+                    // (whether plan itself is complex or not).
+                    //
+                    tmpmemsize = Math.Max(tmpmemsize, 2*n1*n2);
+                    plan.plan[entryoffset+0] = esize;
+                    plan.plan[entryoffset+1] = n1;
+                    plan.plan[entryoffset+2] = n2;
+                    if( tasktype==ftbasecffttask )
+                    {
+                        plan.plan[entryoffset+3] = fftcooleytukeyplan;
+                    }
+                    else
+                    {
+                        plan.plan[entryoffset+3] = fftrealcooleytukeyplan;
+                    }
+                    plan.plan[entryoffset+4] = 0;
+                    plan.plan[entryoffset+5] = plansize;
+                    ftbasegenerateplanrec(n1, ftbasecffttask, plan, ref plansize, ref precomputedsize, ref planarraysize, ref tmpmemsize, ref stackmemsize, stackptr);
+                    plan.plan[entryoffset+6] = plansize;
+                    ftbasegenerateplanrec(n2, ftbasecffttask, plan, ref plansize, ref precomputedsize, ref planarraysize, ref tmpmemsize, ref stackmemsize, stackptr);
+                    plan.plan[entryoffset+7] = -1;
+                    return;
+                }
+                else
+                {
+                    if( ((n==2 || n==3) || n==4) || n==5 )
+                    {
+                        
+                        //
+                        // hard-coded plan
+                        //
+                        plan.plan[entryoffset+0] = esize;
+                        plan.plan[entryoffset+1] = n1;
+                        plan.plan[entryoffset+2] = n2;
+                        plan.plan[entryoffset+3] = fftcodeletplan;
+                        plan.plan[entryoffset+4] = 0;
+                        plan.plan[entryoffset+5] = -1;
+                        plan.plan[entryoffset+6] = -1;
+                        plan.plan[entryoffset+7] = precomputedsize;
+                        if( n==3 )
+                        {
+                            precomputedsize = precomputedsize+2;
+                        }
+                        if( n==5 )
+                        {
+                            precomputedsize = precomputedsize+5;
+                        }
+                        return;
+                    }
+                    else
+                    {
+                        
+                        //
+                        // Bluestein's plan
+                        //
+                        // Select such M that M>=2*N-1, M is composite, and M's
+                        // factors are 2, 3, 5
+                        //
+                        k = 2*n2-1;
+                        m = ftbasefindsmooth(k);
+                        tmpmemsize = Math.Max(tmpmemsize, 2*m);
+                        plan.plan[entryoffset+0] = esize;
+                        plan.plan[entryoffset+1] = n2;
+                        plan.plan[entryoffset+2] = -1;
+                        plan.plan[entryoffset+3] = fftbluesteinplan;
+                        plan.plan[entryoffset+4] = m;
+                        plan.plan[entryoffset+5] = plansize;
+                        stackptr = stackptr+2*2*m;
+                        stackmemsize = Math.Max(stackmemsize, stackptr);
+                        ftbasegenerateplanrec(m, ftbasecffttask, plan, ref plansize, ref precomputedsize, ref planarraysize, ref tmpmemsize, ref stackmemsize, stackptr);
+                        stackptr = stackptr-2*2*m;
+                        plan.plan[entryoffset+6] = -1;
+                        plan.plan[entryoffset+7] = precomputedsize;
+                        precomputedsize = precomputedsize+2*m+2*n;
+                        return;
+                    }
+                }
+            }
+            if( tasktype==ftbaserfhttask )
+            {
+                
+                //
+                // real FHT plans
+                //
+                if( n1!=1 )
+                {
+                    
+                    //
+                    // Cooley-Tukey plan
+                    //
+                    //
+                    tmpmemsize = Math.Max(tmpmemsize, 2*n1*n2);
+                    plan.plan[entryoffset+0] = esize;
+                    plan.plan[entryoffset+1] = n1;
+                    plan.plan[entryoffset+2] = n2;
+                    plan.plan[entryoffset+3] = fhtcooleytukeyplan;
+                    plan.plan[entryoffset+4] = 0;
+                    plan.plan[entryoffset+5] = plansize;
+                    ftbasegenerateplanrec(n1, tasktype, plan, ref plansize, ref precomputedsize, ref planarraysize, ref tmpmemsize, ref stackmemsize, stackptr);
+                    plan.plan[entryoffset+6] = plansize;
+                    ftbasegenerateplanrec(n2, tasktype, plan, ref plansize, ref precomputedsize, ref planarraysize, ref tmpmemsize, ref stackmemsize, stackptr);
+                    plan.plan[entryoffset+7] = -1;
+                    return;
+                }
+                else
+                {
+                    
+                    //
+                    // N2 plan
+                    //
+                    plan.plan[entryoffset+0] = esize;
+                    plan.plan[entryoffset+1] = n1;
+                    plan.plan[entryoffset+2] = n2;
+                    plan.plan[entryoffset+3] = fhtn2plan;
+                    plan.plan[entryoffset+4] = 0;
+                    plan.plan[entryoffset+5] = -1;
+                    plan.plan[entryoffset+6] = -1;
+                    plan.plan[entryoffset+7] = -1;
+                    if( ((n==2 || n==3) || n==4) || n==5 )
+                    {
+                        
+                        //
+                        // hard-coded plan
+                        //
+                        plan.plan[entryoffset+0] = esize;
+                        plan.plan[entryoffset+1] = n1;
+                        plan.plan[entryoffset+2] = n2;
+                        plan.plan[entryoffset+3] = fhtcodeletplan;
+                        plan.plan[entryoffset+4] = 0;
+                        plan.plan[entryoffset+5] = -1;
+                        plan.plan[entryoffset+6] = -1;
+                        plan.plan[entryoffset+7] = precomputedsize;
+                        if( n==3 )
+                        {
+                            precomputedsize = precomputedsize+2;
+                        }
+                        if( n==5 )
+                        {
+                            precomputedsize = precomputedsize+5;
+                        }
+                        return;
+                    }
+                    return;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Recurrent subroutine for precomputing FFT plans
+
+          -- ALGLIB --
+             Copyright 01.05.2009 by Bochkanov Sergey
+        *************************************************************************/
+        private static void ftbaseprecomputeplanrec(ftplan plan,
+            int entryoffset,
+            int stackptr)
+        {
+            int i = 0;
+            int n1 = 0;
+            int n2 = 0;
+            int n = 0;
+            int m = 0;
+            int offs = 0;
+            double v = 0;
+            double[] emptyarray = new double[0];
+            double bx = 0;
+            double by = 0;
+
+            if( (plan.plan[entryoffset+3]==fftcooleytukeyplan || plan.plan[entryoffset+3]==fftrealcooleytukeyplan) || plan.plan[entryoffset+3]==fhtcooleytukeyplan )
+            {
+                ftbaseprecomputeplanrec(plan, plan.plan[entryoffset+5], stackptr);
+                ftbaseprecomputeplanrec(plan, plan.plan[entryoffset+6], stackptr);
+                return;
+            }
+            if( plan.plan[entryoffset+3]==fftcodeletplan || plan.plan[entryoffset+3]==fhtcodeletplan )
+            {
+                n1 = plan.plan[entryoffset+1];
+                n2 = plan.plan[entryoffset+2];
+                n = n1*n2;
+                if( n==3 )
+                {
+                    offs = plan.plan[entryoffset+7];
+                    plan.precomputed[offs+0] = Math.Cos(2*Math.PI/3)-1;
+                    plan.precomputed[offs+1] = Math.Sin(2*Math.PI/3);
+                    return;
+                }
+                if( n==5 )
+                {
+                    offs = plan.plan[entryoffset+7];
+                    v = 2*Math.PI/5;
+                    plan.precomputed[offs+0] = (Math.Cos(v)+Math.Cos(2*v))/2-1;
+                    plan.precomputed[offs+1] = (Math.Cos(v)-Math.Cos(2*v))/2;
+                    plan.precomputed[offs+2] = -Math.Sin(v);
+                    plan.precomputed[offs+3] = -(Math.Sin(v)+Math.Sin(2*v));
+                    plan.precomputed[offs+4] = Math.Sin(v)-Math.Sin(2*v);
+                    return;
+                }
+            }
+            if( plan.plan[entryoffset+3]==fftbluesteinplan )
+            {
+                ftbaseprecomputeplanrec(plan, plan.plan[entryoffset+5], stackptr);
+                n = plan.plan[entryoffset+1];
+                m = plan.plan[entryoffset+4];
+                offs = plan.plan[entryoffset+7];
+                for(i=0; i<=2*m-1; i++)
+                {
+                    plan.precomputed[offs+i] = 0;
+                }
+                for(i=0; i<=n-1; i++)
+                {
+                    bx = Math.Cos(Math.PI*math.sqr(i)/n);
+                    by = Math.Sin(Math.PI*math.sqr(i)/n);
+                    plan.precomputed[offs+2*i+0] = bx;
+                    plan.precomputed[offs+2*i+1] = by;
+                    plan.precomputed[offs+2*m+2*i+0] = bx;
+                    plan.precomputed[offs+2*m+2*i+1] = by;
+                    if( i>0 )
+                    {
+                        plan.precomputed[offs+2*(m-i)+0] = bx;
+                        plan.precomputed[offs+2*(m-i)+1] = by;
+                    }
+                }
+                ftbaseexecuteplanrec(ref plan.precomputed, offs, plan, plan.plan[entryoffset+5], stackptr);
+                return;
+            }
+        }
+
+
+        /*************************************************************************
+        Twiddle factors calculation
+
+          -- ALGLIB --
+             Copyright 01.05.2009 by Bochkanov Sergey
+        *************************************************************************/
+        private static void ffttwcalc(ref double[] a,
+            int aoffset,
+            int n1,
+            int n2)
+        {
+            int i = 0;
+            int j = 0;
+            int n = 0;
+            int idx = 0;
+            int offs = 0;
+            double x = 0;
+            double y = 0;
+            double twxm1 = 0;
+            double twy = 0;
+            double twbasexm1 = 0;
+            double twbasey = 0;
+            double twrowxm1 = 0;
+            double twrowy = 0;
+            double tmpx = 0;
+            double tmpy = 0;
+            double v = 0;
+
+            n = n1*n2;
+            v = -(2*Math.PI/n);
+            twbasexm1 = -(2*math.sqr(Math.Sin(0.5*v)));
+            twbasey = Math.Sin(v);
+            twrowxm1 = 0;
+            twrowy = 0;
+            for(i=0; i<=n2-1; i++)
+            {
+                twxm1 = 0;
+                twy = 0;
+                for(j=0; j<=n1-1; j++)
+                {
+                    idx = i*n1+j;
+                    offs = aoffset+2*idx;
+                    x = a[offs+0];
+                    y = a[offs+1];
+                    tmpx = x*twxm1-y*twy;
+                    tmpy = x*twy+y*twxm1;
+                    a[offs+0] = x+tmpx;
+                    a[offs+1] = y+tmpy;
+                    
+                    //
+                    // update Tw: Tw(new) = Tw(old)*TwRow
+                    //
+                    if( j<n1-1 )
+                    {
+                        if( j%ftbaseupdatetw==0 )
+                        {
+                            v = -(2*Math.PI*i*(j+1)/n);
+                            twxm1 = -(2*math.sqr(Math.Sin(0.5*v)));
+                            twy = Math.Sin(v);
+                        }
+                        else
+                        {
+                            tmpx = twrowxm1+twxm1*twrowxm1-twy*twrowy;
+                            tmpy = twrowy+twxm1*twrowy+twy*twrowxm1;
+                            twxm1 = twxm1+tmpx;
+                            twy = twy+tmpy;
+                        }
+                    }
+                }
+                
+                //
+                // update TwRow: TwRow(new) = TwRow(old)*TwBase
+                //
+                if( i<n2-1 )
+                {
+                    if( j%ftbaseupdatetw==0 )
+                    {
+                        v = -(2*Math.PI*(i+1)/n);
+                        twrowxm1 = -(2*math.sqr(Math.Sin(0.5*v)));
+                        twrowy = Math.Sin(v);
+                    }
+                    else
+                    {
+                        tmpx = twbasexm1+twrowxm1*twbasexm1-twrowy*twbasey;
+                        tmpy = twbasey+twrowxm1*twbasey+twrowy*twbasexm1;
+                        twrowxm1 = twrowxm1+tmpx;
+                        twrowy = twrowy+tmpy;
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Linear transpose: transpose complex matrix stored in 1-dimensional array
+
+          -- ALGLIB --
+             Copyright 01.05.2009 by Bochkanov Sergey
+        *************************************************************************/
+        private static void internalcomplexlintranspose(ref double[] a,
+            int m,
+            int n,
+            int astart,
+            ref double[] buf)
+        {
+            int i_ = 0;
+            int i1_ = 0;
+
+            ffticltrec(ref a, astart, n, ref buf, 0, m, m, n);
+            i1_ = (0) - (astart);
+            for(i_=astart; i_<=astart+2*m*n-1;i_++)
+            {
+                a[i_] = buf[i_+i1_];
+            }
+        }
+
+
+        /*************************************************************************
+        Linear transpose: transpose real matrix stored in 1-dimensional array
+
+          -- ALGLIB --
+             Copyright 01.05.2009 by Bochkanov Sergey
+        *************************************************************************/
+        private static void internalreallintranspose(ref double[] a,
+            int m,
+            int n,
+            int astart,
+            ref double[] buf)
+        {
+            int i_ = 0;
+            int i1_ = 0;
+
+            fftirltrec(ref a, astart, n, ref buf, 0, m, m, n);
+            i1_ = (0) - (astart);
+            for(i_=astart; i_<=astart+m*n-1;i_++)
+            {
+                a[i_] = buf[i_+i1_];
+            }
+        }
+
+
+        /*************************************************************************
+        Recurrent subroutine for a InternalComplexLinTranspose
+
+        Write A^T to B, where:
+        * A is m*n complex matrix stored in array A as pairs of real/image values,
+          beginning from AStart position, with AStride stride
+        * B is n*m complex matrix stored in array B as pairs of real/image values,
+          beginning from BStart position, with BStride stride
+        stride is measured in complex numbers, i.e. in real/image pairs.
+
+          -- ALGLIB --
+             Copyright 01.05.2009 by Bochkanov Sergey
+        *************************************************************************/
+        private static void ffticltrec(ref double[] a,
+            int astart,
+            int astride,
+            ref double[] b,
+            int bstart,
+            int bstride,
+            int m,
+            int n)
+        {
+            int i = 0;
+            int j = 0;
+            int idx1 = 0;
+            int idx2 = 0;
+            int m2 = 0;
+            int m1 = 0;
+            int n1 = 0;
+
+            if( m==0 || n==0 )
+            {
+                return;
+            }
+            if( Math.Max(m, n)<=8 )
+            {
+                m2 = 2*bstride;
+                for(i=0; i<=m-1; i++)
+                {
+                    idx1 = bstart+2*i;
+                    idx2 = astart+2*i*astride;
+                    for(j=0; j<=n-1; j++)
+                    {
+                        b[idx1+0] = a[idx2+0];
+                        b[idx1+1] = a[idx2+1];
+                        idx1 = idx1+m2;
+                        idx2 = idx2+2;
+                    }
+                }
+                return;
+            }
+            if( n>m )
+            {
+                
+                //
+                // New partition:
+                //
+                // "A^T -> B" becomes "(A1 A2)^T -> ( B1 )
+                //                                  ( B2 )
+                //
+                n1 = n/2;
+                if( n-n1>=8 && n1%8!=0 )
+                {
+                    n1 = n1+(8-n1%8);
+                }
+                alglib.ap.assert(n-n1>0);
+                ffticltrec(ref a, astart, astride, ref b, bstart, bstride, m, n1);
+                ffticltrec(ref a, astart+2*n1, astride, ref b, bstart+2*n1*bstride, bstride, m, n-n1);
+            }
+            else
+            {
+                
+                //
+                // New partition:
+                //
+                // "A^T -> B" becomes "( A1 )^T -> ( B1 B2 )
+                //                     ( A2 )
+                //
+                m1 = m/2;
+                if( m-m1>=8 && m1%8!=0 )
+                {
+                    m1 = m1+(8-m1%8);
+                }
+                alglib.ap.assert(m-m1>0);
+                ffticltrec(ref a, astart, astride, ref b, bstart, bstride, m1, n);
+                ffticltrec(ref a, astart+2*m1*astride, astride, ref b, bstart+2*m1, bstride, m-m1, n);
+            }
+        }
+
+
+        /*************************************************************************
+        Recurrent subroutine for a InternalRealLinTranspose
+
+
+          -- ALGLIB --
+             Copyright 01.05.2009 by Bochkanov Sergey
+        *************************************************************************/
+        private static void fftirltrec(ref double[] a,
+            int astart,
+            int astride,
+            ref double[] b,
+            int bstart,
+            int bstride,
+            int m,
+            int n)
+        {
+            int i = 0;
+            int j = 0;
+            int idx1 = 0;
+            int idx2 = 0;
+            int m1 = 0;
+            int n1 = 0;
+
+            if( m==0 || n==0 )
+            {
+                return;
+            }
+            if( Math.Max(m, n)<=8 )
+            {
+                for(i=0; i<=m-1; i++)
+                {
+                    idx1 = bstart+i;
+                    idx2 = astart+i*astride;
+                    for(j=0; j<=n-1; j++)
+                    {
+                        b[idx1] = a[idx2];
+                        idx1 = idx1+bstride;
+                        idx2 = idx2+1;
+                    }
+                }
+                return;
+            }
+            if( n>m )
+            {
+                
+                //
+                // New partition:
+                //
+                // "A^T -> B" becomes "(A1 A2)^T -> ( B1 )
+                //                                  ( B2 )
+                //
+                n1 = n/2;
+                if( n-n1>=8 && n1%8!=0 )
+                {
+                    n1 = n1+(8-n1%8);
+                }
+                alglib.ap.assert(n-n1>0);
+                fftirltrec(ref a, astart, astride, ref b, bstart, bstride, m, n1);
+                fftirltrec(ref a, astart+n1, astride, ref b, bstart+n1*bstride, bstride, m, n-n1);
+            }
+            else
+            {
+                
+                //
+                // New partition:
+                //
+                // "A^T -> B" becomes "( A1 )^T -> ( B1 B2 )
+                //                     ( A2 )
+                //
+                m1 = m/2;
+                if( m-m1>=8 && m1%8!=0 )
+                {
+                    m1 = m1+(8-m1%8);
+                }
+                alglib.ap.assert(m-m1>0);
+                fftirltrec(ref a, astart, astride, ref b, bstart, bstride, m1, n);
+                fftirltrec(ref a, astart+m1*astride, astride, ref b, bstart+m1, bstride, m-m1, n);
+            }
+        }
+
+
+        /*************************************************************************
+        recurrent subroutine for FFTFindSmoothRec
+
+          -- ALGLIB --
+             Copyright 01.05.2009 by Bochkanov Sergey
+        *************************************************************************/
+        private static void ftbasefindsmoothrec(int n,
+            int seed,
+            int leastfactor,
+            ref int best)
+        {
+            alglib.ap.assert(ftbasemaxsmoothfactor<=5, "FTBaseFindSmoothRec: internal error!");
+            if( seed>=n )
+            {
+                best = Math.Min(best, seed);
+                return;
+            }
+            if( leastfactor<=2 )
+            {
+                ftbasefindsmoothrec(n, seed*2, 2, ref best);
+            }
+            if( leastfactor<=3 )
+            {
+                ftbasefindsmoothrec(n, seed*3, 3, ref best);
+            }
+            if( leastfactor<=5 )
+            {
+                ftbasefindsmoothrec(n, seed*5, 5, ref best);
+            }
+        }
+
+
+        /*************************************************************************
+        Internal subroutine: array resize
+
+          -- ALGLIB --
+             Copyright 01.05.2009 by Bochkanov Sergey
+        *************************************************************************/
+        private static void fftarrayresize(ref int[] a,
+            ref int asize,
+            int newasize)
+        {
+            int[] tmp = new int[0];
+            int i = 0;
+
+            tmp = new int[asize];
+            for(i=0; i<=asize-1; i++)
+            {
+                tmp[i] = a[i];
+            }
+            a = new int[newasize];
+            for(i=0; i<=asize-1; i++)
+            {
+                a[i] = tmp[i];
+            }
+            asize = newasize;
+        }
+
+
+        /*************************************************************************
+        Reference FHT stub
+        *************************************************************************/
+        private static void reffht(ref double[] a,
+            int n,
+            int offs)
+        {
+            double[] buf = new double[0];
+            int i = 0;
+            int j = 0;
+            double v = 0;
+
+            alglib.ap.assert(n>0, "RefFHTR1D: incorrect N!");
+            buf = new double[n];
+            for(i=0; i<=n-1; i++)
+            {
+                v = 0;
+                for(j=0; j<=n-1; j++)
+                {
+                    v = v+a[offs+j]*(Math.Cos(2*Math.PI*i*j/n)+Math.Sin(2*Math.PI*i*j/n));
+                }
+                buf[i] = v;
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                a[offs+i] = buf[i];
+            }
+        }
+
+
+    }
+    public class nearunityunit
+    {
+        public static double nulog1p(double x)
+        {
+            double result = 0;
+            double z = 0;
+            double lp = 0;
+            double lq = 0;
+
+            z = 1.0+x;
+            if( (double)(z)<(double)(0.70710678118654752440) || (double)(z)>(double)(1.41421356237309504880) )
+            {
+                result = Math.Log(z);
+                return result;
+            }
+            z = x*x;
+            lp = 4.5270000862445199635215E-5;
+            lp = lp*x+4.9854102823193375972212E-1;
+            lp = lp*x+6.5787325942061044846969E0;
+            lp = lp*x+2.9911919328553073277375E1;
+            lp = lp*x+6.0949667980987787057556E1;
+            lp = lp*x+5.7112963590585538103336E1;
+            lp = lp*x+2.0039553499201281259648E1;
+            lq = 1.0000000000000000000000E0;
+            lq = lq*x+1.5062909083469192043167E1;
+            lq = lq*x+8.3047565967967209469434E1;
+            lq = lq*x+2.2176239823732856465394E2;
+            lq = lq*x+3.0909872225312059774938E2;
+            lq = lq*x+2.1642788614495947685003E2;
+            lq = lq*x+6.0118660497603843919306E1;
+            z = -(0.5*z)+x*(z*lp/lq);
+            result = x+z;
+            return result;
+        }
+
+
+        public static double nuexpm1(double x)
+        {
+            double result = 0;
+            double r = 0;
+            double xx = 0;
+            double ep = 0;
+            double eq = 0;
+
+            if( (double)(x)<(double)(-0.5) || (double)(x)>(double)(0.5) )
+            {
+                result = Math.Exp(x)-1.0;
+                return result;
+            }
+            xx = x*x;
+            ep = 1.2617719307481059087798E-4;
+            ep = ep*xx+3.0299440770744196129956E-2;
+            ep = ep*xx+9.9999999999999999991025E-1;
+            eq = 3.0019850513866445504159E-6;
+            eq = eq*xx+2.5244834034968410419224E-3;
+            eq = eq*xx+2.2726554820815502876593E-1;
+            eq = eq*xx+2.0000000000000000000897E0;
+            r = x*ep;
+            r = r/(eq-r);
+            result = r+r;
+            return result;
+        }
+
+
+        public static double nucosm1(double x)
+        {
+            double result = 0;
+            double xx = 0;
+            double c = 0;
+
+            if( (double)(x)<(double)(-(0.25*Math.PI)) || (double)(x)>(double)(0.25*Math.PI) )
+            {
+                result = Math.Cos(x)-1;
+                return result;
+            }
+            xx = x*x;
+            c = 4.7377507964246204691685E-14;
+            c = c*xx-1.1470284843425359765671E-11;
+            c = c*xx+2.0876754287081521758361E-9;
+            c = c*xx-2.7557319214999787979814E-7;
+            c = c*xx+2.4801587301570552304991E-5;
+            c = c*xx-1.3888888888888872993737E-3;
+            c = c*xx+4.1666666666666666609054E-2;
+            result = -(0.5*xx)+xx*xx*c;
+            return result;
+        }
+
+
+    }
+    public class alglibbasics
+    {
+
+
+    }
+}
+
Index: /trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.5.0/ALGLIB-3.5.0/alglibmisc.cs
===================================================================
--- /trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.5.0/ALGLIB-3.5.0/alglibmisc.cs	(revision 7673)
+++ /trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.5.0/ALGLIB-3.5.0/alglibmisc.cs	(revision 7673)
@@ -0,0 +1,2785 @@
+/*************************************************************************
+Copyright (c) Sergey Bochkanov (ALGLIB project).
+
+>>> SOURCE LICENSE >>>
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation (www.fsf.org); either version 2 of the
+License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+A copy of the GNU General Public License is available at
+http://www.fsf.org/licensing/licenses
+>>> END OF LICENSE >>>
+*************************************************************************/
+#pragma warning disable 162
+#pragma warning disable 219
+using System;
+
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    Portable high quality random number generator state.
+    Initialized with HQRNDRandomize() or HQRNDSeed().
+
+    Fields:
+        S1, S2      -   seed values
+        V           -   precomputed value
+        MagicV      -   'magic' value used to determine whether State structure
+                        was correctly initialized.
+    *************************************************************************/
+    public class hqrndstate
+    {
+        //
+        // Public declarations
+        //
+
+        public hqrndstate()
+        {
+            _innerobj = new hqrnd.hqrndstate();
+        }
+
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private hqrnd.hqrndstate _innerobj;
+        public hqrnd.hqrndstate innerobj { get { return _innerobj; } }
+        public hqrndstate(hqrnd.hqrndstate obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+    /*************************************************************************
+    HQRNDState  initialization  with  random  values  which come from standard
+    RNG.
+
+      -- ALGLIB --
+         Copyright 02.12.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void hqrndrandomize(out hqrndstate state)
+    {
+        state = new hqrndstate();
+        hqrnd.hqrndrandomize(state.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    HQRNDState initialization with seed values
+
+      -- ALGLIB --
+         Copyright 02.12.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void hqrndseed(int s1, int s2, out hqrndstate state)
+    {
+        state = new hqrndstate();
+        hqrnd.hqrndseed(s1, s2, state.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    This function generates random real number in (0,1),
+    not including interval boundaries
+
+    State structure must be initialized with HQRNDRandomize() or HQRNDSeed().
+
+      -- ALGLIB --
+         Copyright 02.12.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static double hqrnduniformr(hqrndstate state)
+    {
+
+        double result = hqrnd.hqrnduniformr(state.innerobj);
+        return result;
+    }
+
+    /*************************************************************************
+    This function generates random integer number in [0, N)
+
+    1. N must be less than HQRNDMax-1.
+    2. State structure must be initialized with HQRNDRandomize() or HQRNDSeed()
+
+      -- ALGLIB --
+         Copyright 02.12.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static int hqrnduniformi(hqrndstate state, int n)
+    {
+
+        int result = hqrnd.hqrnduniformi(state.innerobj, n);
+        return result;
+    }
+
+    /*************************************************************************
+    Random number generator: normal numbers
+
+    This function generates one random number from normal distribution.
+    Its performance is equal to that of HQRNDNormal2()
+
+    State structure must be initialized with HQRNDRandomize() or HQRNDSeed().
+
+      -- ALGLIB --
+         Copyright 02.12.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static double hqrndnormal(hqrndstate state)
+    {
+
+        double result = hqrnd.hqrndnormal(state.innerobj);
+        return result;
+    }
+
+    /*************************************************************************
+    Random number generator: random X and Y such that X^2+Y^2=1
+
+    State structure must be initialized with HQRNDRandomize() or HQRNDSeed().
+
+      -- ALGLIB --
+         Copyright 02.12.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void hqrndunit2(hqrndstate state, out double x, out double y)
+    {
+        x = 0;
+        y = 0;
+        hqrnd.hqrndunit2(state.innerobj, ref x, ref y);
+        return;
+    }
+
+    /*************************************************************************
+    Random number generator: normal numbers
+
+    This function generates two independent random numbers from normal
+    distribution. Its performance is equal to that of HQRNDNormal()
+
+    State structure must be initialized with HQRNDRandomize() or HQRNDSeed().
+
+      -- ALGLIB --
+         Copyright 02.12.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void hqrndnormal2(hqrndstate state, out double x1, out double x2)
+    {
+        x1 = 0;
+        x2 = 0;
+        hqrnd.hqrndnormal2(state.innerobj, ref x1, ref x2);
+        return;
+    }
+
+    /*************************************************************************
+    Random number generator: exponential distribution
+
+    State structure must be initialized with HQRNDRandomize() or HQRNDSeed().
+
+      -- ALGLIB --
+         Copyright 11.08.2007 by Bochkanov Sergey
+    *************************************************************************/
+    public static double hqrndexponential(hqrndstate state, double lambdav)
+    {
+
+        double result = hqrnd.hqrndexponential(state.innerobj, lambdav);
+        return result;
+    }
+
+    /*************************************************************************
+    This function generates  random number from discrete distribution given by
+    finite sample X.
+
+    INPUT PARAMETERS
+        State   -   high quality random number generator, must be
+                    initialized with HQRNDRandomize() or HQRNDSeed().
+            X   -   finite sample
+            N   -   number of elements to use, N>=1
+
+    RESULT
+        this function returns one of the X[i] for random i=0..N-1
+
+      -- ALGLIB --
+         Copyright 08.11.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static double hqrnddiscrete(hqrndstate state, double[] x, int n)
+    {
+
+        double result = hqrnd.hqrnddiscrete(state.innerobj, x, n);
+        return result;
+    }
+
+    /*************************************************************************
+    This function generates random number from continuous  distribution  given
+    by finite sample X.
+
+    INPUT PARAMETERS
+        State   -   high quality random number generator, must be
+                    initialized with HQRNDRandomize() or HQRNDSeed().
+            X   -   finite sample, array[N] (can be larger, in this  case only
+                    leading N elements are used). THIS ARRAY MUST BE SORTED BY
+                    ASCENDING.
+            N   -   number of elements to use, N>=1
+
+    RESULT
+        this function returns random number from continuous distribution which
+        tries to approximate X as mush as possible. min(X)<=Result<=max(X).
+
+      -- ALGLIB --
+         Copyright 08.11.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static double hqrndcontinuous(hqrndstate state, double[] x, int n)
+    {
+
+        double result = hqrnd.hqrndcontinuous(state.innerobj, x, n);
+        return result;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+
+    *************************************************************************/
+    public class kdtree
+    {
+        //
+        // Public declarations
+        //
+
+        public kdtree()
+        {
+            _innerobj = new nearestneighbor.kdtree();
+        }
+
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private nearestneighbor.kdtree _innerobj;
+        public nearestneighbor.kdtree innerobj { get { return _innerobj; } }
+        public kdtree(nearestneighbor.kdtree obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+
+    /*************************************************************************
+    This function serializes data structure to string.
+
+    Important properties of s_out:
+    * it contains alphanumeric characters, dots, underscores, minus signs
+    * these symbols are grouped into words, which are separated by spaces
+      and Windows-style (CR+LF) newlines
+    * although  serializer  uses  spaces and CR+LF as separators, you can 
+      replace any separator character by arbitrary combination of spaces,
+      tabs, Windows or Unix newlines. It allows flexible reformatting  of
+      the  string  in  case you want to include it into text or XML file. 
+      But you should not insert separators into the middle of the "words"
+      nor you should change case of letters.
+    * s_out can be freely moved between 32-bit and 64-bit systems, little
+      and big endian machines, and so on. You can serialize structure  on
+      32-bit machine and unserialize it on 64-bit one (or vice versa), or
+      serialize  it  on  SPARC  and  unserialize  on  x86.  You  can also 
+      serialize  it  in  C# version of ALGLIB and unserialize in C++ one, 
+      and vice versa.
+    *************************************************************************/
+    public static void kdtreeserialize(kdtree obj, out string s_out)
+    {
+        alglib.serializer s = new alglib.serializer();
+        s.alloc_start();
+        nearestneighbor.kdtreealloc(s, obj.innerobj);
+        s.sstart_str();
+        nearestneighbor.kdtreeserialize(s, obj.innerobj);
+        s.stop();
+        s_out = s.get_string();
+    }
+
+
+    /*************************************************************************
+    This function unserializes data structure from string.
+    *************************************************************************/
+    public static void kdtreeunserialize(string s_in, out kdtree obj)
+    {
+        alglib.serializer s = new alglib.serializer();
+        obj = new kdtree();
+        s.ustart_str(s_in);
+        nearestneighbor.kdtreeunserialize(s, obj.innerobj);
+        s.stop();
+    }
+
+    /*************************************************************************
+    KD-tree creation
+
+    This subroutine creates KD-tree from set of X-values and optional Y-values
+
+    INPUT PARAMETERS
+        XY      -   dataset, array[0..N-1,0..NX+NY-1].
+                    one row corresponds to one point.
+                    first NX columns contain X-values, next NY (NY may be zero)
+                    columns may contain associated Y-values
+        N       -   number of points, N>=0.
+        NX      -   space dimension, NX>=1.
+        NY      -   number of optional Y-values, NY>=0.
+        NormType-   norm type:
+                    * 0 denotes infinity-norm
+                    * 1 denotes 1-norm
+                    * 2 denotes 2-norm (Euclidean norm)
+
+    OUTPUT PARAMETERS
+        KDT     -   KD-tree
+
+
+    NOTES
+
+    1. KD-tree  creation  have O(N*logN) complexity and O(N*(2*NX+NY))  memory
+       requirements.
+    2. Although KD-trees may be used with any combination of N  and  NX,  they
+       are more efficient than brute-force search only when N >> 4^NX. So they
+       are most useful in low-dimensional tasks (NX=2, NX=3). NX=1  is another
+       inefficient case, because  simple  binary  search  (without  additional
+       structures) is much more efficient in such tasks than KD-trees.
+
+      -- ALGLIB --
+         Copyright 28.02.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void kdtreebuild(double[,] xy, int n, int nx, int ny, int normtype, out kdtree kdt)
+    {
+        kdt = new kdtree();
+        nearestneighbor.kdtreebuild(xy, n, nx, ny, normtype, kdt.innerobj);
+        return;
+    }
+    public static void kdtreebuild(double[,] xy, int nx, int ny, int normtype, out kdtree kdt)
+    {
+        int n;
+
+        kdt = new kdtree();
+        n = ap.rows(xy);
+        nearestneighbor.kdtreebuild(xy, n, nx, ny, normtype, kdt.innerobj);
+
+        return;
+    }
+
+    /*************************************************************************
+    KD-tree creation
+
+    This  subroutine  creates  KD-tree  from set of X-values, integer tags and
+    optional Y-values
+
+    INPUT PARAMETERS
+        XY      -   dataset, array[0..N-1,0..NX+NY-1].
+                    one row corresponds to one point.
+                    first NX columns contain X-values, next NY (NY may be zero)
+                    columns may contain associated Y-values
+        Tags    -   tags, array[0..N-1], contains integer tags associated
+                    with points.
+        N       -   number of points, N>=0
+        NX      -   space dimension, NX>=1.
+        NY      -   number of optional Y-values, NY>=0.
+        NormType-   norm type:
+                    * 0 denotes infinity-norm
+                    * 1 denotes 1-norm
+                    * 2 denotes 2-norm (Euclidean norm)
+
+    OUTPUT PARAMETERS
+        KDT     -   KD-tree
+
+    NOTES
+
+    1. KD-tree  creation  have O(N*logN) complexity and O(N*(2*NX+NY))  memory
+       requirements.
+    2. Although KD-trees may be used with any combination of N  and  NX,  they
+       are more efficient than brute-force search only when N >> 4^NX. So they
+       are most useful in low-dimensional tasks (NX=2, NX=3). NX=1  is another
+       inefficient case, because  simple  binary  search  (without  additional
+       structures) is much more efficient in such tasks than KD-trees.
+
+      -- ALGLIB --
+         Copyright 28.02.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void kdtreebuildtagged(double[,] xy, int[] tags, int n, int nx, int ny, int normtype, out kdtree kdt)
+    {
+        kdt = new kdtree();
+        nearestneighbor.kdtreebuildtagged(xy, tags, n, nx, ny, normtype, kdt.innerobj);
+        return;
+    }
+    public static void kdtreebuildtagged(double[,] xy, int[] tags, int nx, int ny, int normtype, out kdtree kdt)
+    {
+        int n;
+        if( (ap.rows(xy)!=ap.len(tags)))
+            throw new alglibexception("Error while calling 'kdtreebuildtagged': looks like one of arguments has wrong size");
+        kdt = new kdtree();
+        n = ap.rows(xy);
+        nearestneighbor.kdtreebuildtagged(xy, tags, n, nx, ny, normtype, kdt.innerobj);
+
+        return;
+    }
+
+    /*************************************************************************
+    K-NN query: K nearest neighbors
+
+    INPUT PARAMETERS
+        KDT         -   KD-tree
+        X           -   point, array[0..NX-1].
+        K           -   number of neighbors to return, K>=1
+        SelfMatch   -   whether self-matches are allowed:
+                        * if True, nearest neighbor may be the point itself
+                          (if it exists in original dataset)
+                        * if False, then only points with non-zero distance
+                          are returned
+                        * if not given, considered True
+
+    RESULT
+        number of actual neighbors found (either K or N, if K>N).
+
+    This  subroutine  performs  query  and  stores  its result in the internal
+    structures of the KD-tree. You can use  following  subroutines  to  obtain
+    these results:
+    * KDTreeQueryResultsX() to get X-values
+    * KDTreeQueryResultsXY() to get X- and Y-values
+    * KDTreeQueryResultsTags() to get tag values
+    * KDTreeQueryResultsDistances() to get distances
+
+      -- ALGLIB --
+         Copyright 28.02.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static int kdtreequeryknn(kdtree kdt, double[] x, int k, bool selfmatch)
+    {
+
+        int result = nearestneighbor.kdtreequeryknn(kdt.innerobj, x, k, selfmatch);
+        return result;
+    }
+    public static int kdtreequeryknn(kdtree kdt, double[] x, int k)
+    {
+        bool selfmatch;
+
+
+        selfmatch = true;
+        int result = nearestneighbor.kdtreequeryknn(kdt.innerobj, x, k, selfmatch);
+
+        return result;
+    }
+
+    /*************************************************************************
+    R-NN query: all points within R-sphere centered at X
+
+    INPUT PARAMETERS
+        KDT         -   KD-tree
+        X           -   point, array[0..NX-1].
+        R           -   radius of sphere (in corresponding norm), R>0
+        SelfMatch   -   whether self-matches are allowed:
+                        * if True, nearest neighbor may be the point itself
+                          (if it exists in original dataset)
+                        * if False, then only points with non-zero distance
+                          are returned
+                        * if not given, considered True
+
+    RESULT
+        number of neighbors found, >=0
+
+    This  subroutine  performs  query  and  stores  its result in the internal
+    structures of the KD-tree. You can use  following  subroutines  to  obtain
+    actual results:
+    * KDTreeQueryResultsX() to get X-values
+    * KDTreeQueryResultsXY() to get X- and Y-values
+    * KDTreeQueryResultsTags() to get tag values
+    * KDTreeQueryResultsDistances() to get distances
+
+      -- ALGLIB --
+         Copyright 28.02.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static int kdtreequeryrnn(kdtree kdt, double[] x, double r, bool selfmatch)
+    {
+
+        int result = nearestneighbor.kdtreequeryrnn(kdt.innerobj, x, r, selfmatch);
+        return result;
+    }
+    public static int kdtreequeryrnn(kdtree kdt, double[] x, double r)
+    {
+        bool selfmatch;
+
+
+        selfmatch = true;
+        int result = nearestneighbor.kdtreequeryrnn(kdt.innerobj, x, r, selfmatch);
+
+        return result;
+    }
+
+    /*************************************************************************
+    K-NN query: approximate K nearest neighbors
+
+    INPUT PARAMETERS
+        KDT         -   KD-tree
+        X           -   point, array[0..NX-1].
+        K           -   number of neighbors to return, K>=1
+        SelfMatch   -   whether self-matches are allowed:
+                        * if True, nearest neighbor may be the point itself
+                          (if it exists in original dataset)
+                        * if False, then only points with non-zero distance
+                          are returned
+                        * if not given, considered True
+        Eps         -   approximation factor, Eps>=0. eps-approximate  nearest
+                        neighbor  is  a  neighbor  whose distance from X is at
+                        most (1+eps) times distance of true nearest neighbor.
+
+    RESULT
+        number of actual neighbors found (either K or N, if K>N).
+
+    NOTES
+        significant performance gain may be achieved only when Eps  is  is  on
+        the order of magnitude of 1 or larger.
+
+    This  subroutine  performs  query  and  stores  its result in the internal
+    structures of the KD-tree. You can use  following  subroutines  to  obtain
+    these results:
+    * KDTreeQueryResultsX() to get X-values
+    * KDTreeQueryResultsXY() to get X- and Y-values
+    * KDTreeQueryResultsTags() to get tag values
+    * KDTreeQueryResultsDistances() to get distances
+
+      -- ALGLIB --
+         Copyright 28.02.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static int kdtreequeryaknn(kdtree kdt, double[] x, int k, bool selfmatch, double eps)
+    {
+
+        int result = nearestneighbor.kdtreequeryaknn(kdt.innerobj, x, k, selfmatch, eps);
+        return result;
+    }
+    public static int kdtreequeryaknn(kdtree kdt, double[] x, int k, double eps)
+    {
+        bool selfmatch;
+
+
+        selfmatch = true;
+        int result = nearestneighbor.kdtreequeryaknn(kdt.innerobj, x, k, selfmatch, eps);
+
+        return result;
+    }
+
+    /*************************************************************************
+    X-values from last query
+
+    INPUT PARAMETERS
+        KDT     -   KD-tree
+        X       -   possibly pre-allocated buffer. If X is too small to store
+                    result, it is resized. If size(X) is enough to store
+                    result, it is left unchanged.
+
+    OUTPUT PARAMETERS
+        X       -   rows are filled with X-values
+
+    NOTES
+    1. points are ordered by distance from the query point (first = closest)
+    2. if  XY is larger than required to store result, only leading part  will
+       be overwritten; trailing part will be left unchanged. So  if  on  input
+       XY = [[A,B],[C,D]], and result is [1,2],  then  on  exit  we  will  get
+       XY = [[1,2],[C,D]]. This is done purposely to increase performance;  if
+       you want function  to  resize  array  according  to  result  size,  use
+       function with same name and suffix 'I'.
+
+    SEE ALSO
+    * KDTreeQueryResultsXY()            X- and Y-values
+    * KDTreeQueryResultsTags()          tag values
+    * KDTreeQueryResultsDistances()     distances
+
+      -- ALGLIB --
+         Copyright 28.02.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void kdtreequeryresultsx(kdtree kdt, ref double[,] x)
+    {
+
+        nearestneighbor.kdtreequeryresultsx(kdt.innerobj, ref x);
+        return;
+    }
+
+    /*************************************************************************
+    X- and Y-values from last query
+
+    INPUT PARAMETERS
+        KDT     -   KD-tree
+        XY      -   possibly pre-allocated buffer. If XY is too small to store
+                    result, it is resized. If size(XY) is enough to store
+                    result, it is left unchanged.
+
+    OUTPUT PARAMETERS
+        XY      -   rows are filled with points: first NX columns with
+                    X-values, next NY columns - with Y-values.
+
+    NOTES
+    1. points are ordered by distance from the query point (first = closest)
+    2. if  XY is larger than required to store result, only leading part  will
+       be overwritten; trailing part will be left unchanged. So  if  on  input
+       XY = [[A,B],[C,D]], and result is [1,2],  then  on  exit  we  will  get
+       XY = [[1,2],[C,D]]. This is done purposely to increase performance;  if
+       you want function  to  resize  array  according  to  result  size,  use
+       function with same name and suffix 'I'.
+
+    SEE ALSO
+    * KDTreeQueryResultsX()             X-values
+    * KDTreeQueryResultsTags()          tag values
+    * KDTreeQueryResultsDistances()     distances
+
+      -- ALGLIB --
+         Copyright 28.02.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void kdtreequeryresultsxy(kdtree kdt, ref double[,] xy)
+    {
+
+        nearestneighbor.kdtreequeryresultsxy(kdt.innerobj, ref xy);
+        return;
+    }
+
+    /*************************************************************************
+    Tags from last query
+
+    INPUT PARAMETERS
+        KDT     -   KD-tree
+        Tags    -   possibly pre-allocated buffer. If X is too small to store
+                    result, it is resized. If size(X) is enough to store
+                    result, it is left unchanged.
+
+    OUTPUT PARAMETERS
+        Tags    -   filled with tags associated with points,
+                    or, when no tags were supplied, with zeros
+
+    NOTES
+    1. points are ordered by distance from the query point (first = closest)
+    2. if  XY is larger than required to store result, only leading part  will
+       be overwritten; trailing part will be left unchanged. So  if  on  input
+       XY = [[A,B],[C,D]], and result is [1,2],  then  on  exit  we  will  get
+       XY = [[1,2],[C,D]]. This is done purposely to increase performance;  if
+       you want function  to  resize  array  according  to  result  size,  use
+       function with same name and suffix 'I'.
+
+    SEE ALSO
+    * KDTreeQueryResultsX()             X-values
+    * KDTreeQueryResultsXY()            X- and Y-values
+    * KDTreeQueryResultsDistances()     distances
+
+      -- ALGLIB --
+         Copyright 28.02.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void kdtreequeryresultstags(kdtree kdt, ref int[] tags)
+    {
+
+        nearestneighbor.kdtreequeryresultstags(kdt.innerobj, ref tags);
+        return;
+    }
+
+    /*************************************************************************
+    Distances from last query
+
+    INPUT PARAMETERS
+        KDT     -   KD-tree
+        R       -   possibly pre-allocated buffer. If X is too small to store
+                    result, it is resized. If size(X) is enough to store
+                    result, it is left unchanged.
+
+    OUTPUT PARAMETERS
+        R       -   filled with distances (in corresponding norm)
+
+    NOTES
+    1. points are ordered by distance from the query point (first = closest)
+    2. if  XY is larger than required to store result, only leading part  will
+       be overwritten; trailing part will be left unchanged. So  if  on  input
+       XY = [[A,B],[C,D]], and result is [1,2],  then  on  exit  we  will  get
+       XY = [[1,2],[C,D]]. This is done purposely to increase performance;  if
+       you want function  to  resize  array  according  to  result  size,  use
+       function with same name and suffix 'I'.
+
+    SEE ALSO
+    * KDTreeQueryResultsX()             X-values
+    * KDTreeQueryResultsXY()            X- and Y-values
+    * KDTreeQueryResultsTags()          tag values
+
+      -- ALGLIB --
+         Copyright 28.02.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void kdtreequeryresultsdistances(kdtree kdt, ref double[] r)
+    {
+
+        nearestneighbor.kdtreequeryresultsdistances(kdt.innerobj, ref r);
+        return;
+    }
+
+    /*************************************************************************
+    X-values from last query; 'interactive' variant for languages like  Python
+    which   support    constructs   like  "X = KDTreeQueryResultsXI(KDT)"  and
+    interactive mode of interpreter.
+
+    This function allocates new array on each call,  so  it  is  significantly
+    slower than its 'non-interactive' counterpart, but it is  more  convenient
+    when you call it from command line.
+
+      -- ALGLIB --
+         Copyright 28.02.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void kdtreequeryresultsxi(kdtree kdt, out double[,] x)
+    {
+        x = new double[0,0];
+        nearestneighbor.kdtreequeryresultsxi(kdt.innerobj, ref x);
+        return;
+    }
+
+    /*************************************************************************
+    XY-values from last query; 'interactive' variant for languages like Python
+    which   support    constructs   like "XY = KDTreeQueryResultsXYI(KDT)" and
+    interactive mode of interpreter.
+
+    This function allocates new array on each call,  so  it  is  significantly
+    slower than its 'non-interactive' counterpart, but it is  more  convenient
+    when you call it from command line.
+
+      -- ALGLIB --
+         Copyright 28.02.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void kdtreequeryresultsxyi(kdtree kdt, out double[,] xy)
+    {
+        xy = new double[0,0];
+        nearestneighbor.kdtreequeryresultsxyi(kdt.innerobj, ref xy);
+        return;
+    }
+
+    /*************************************************************************
+    Tags  from  last  query;  'interactive' variant for languages like  Python
+    which  support  constructs  like "Tags = KDTreeQueryResultsTagsI(KDT)" and
+    interactive mode of interpreter.
+
+    This function allocates new array on each call,  so  it  is  significantly
+    slower than its 'non-interactive' counterpart, but it is  more  convenient
+    when you call it from command line.
+
+      -- ALGLIB --
+         Copyright 28.02.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void kdtreequeryresultstagsi(kdtree kdt, out int[] tags)
+    {
+        tags = new int[0];
+        nearestneighbor.kdtreequeryresultstagsi(kdt.innerobj, ref tags);
+        return;
+    }
+
+    /*************************************************************************
+    Distances from last query; 'interactive' variant for languages like Python
+    which  support  constructs   like  "R = KDTreeQueryResultsDistancesI(KDT)"
+    and interactive mode of interpreter.
+
+    This function allocates new array on each call,  so  it  is  significantly
+    slower than its 'non-interactive' counterpart, but it is  more  convenient
+    when you call it from command line.
+
+      -- ALGLIB --
+         Copyright 28.02.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void kdtreequeryresultsdistancesi(kdtree kdt, out double[] r)
+    {
+        r = new double[0];
+        nearestneighbor.kdtreequeryresultsdistancesi(kdt.innerobj, ref r);
+        return;
+    }
+
+}
+public partial class alglib
+{
+    public class hqrnd
+    {
+        /*************************************************************************
+        Portable high quality random number generator state.
+        Initialized with HQRNDRandomize() or HQRNDSeed().
+
+        Fields:
+            S1, S2      -   seed values
+            V           -   precomputed value
+            MagicV      -   'magic' value used to determine whether State structure
+                            was correctly initialized.
+        *************************************************************************/
+        public class hqrndstate
+        {
+            public int s1;
+            public int s2;
+            public double v;
+            public int magicv;
+        };
+
+
+
+
+        public const int hqrndmax = 2147483563;
+        public const int hqrndm1 = 2147483563;
+        public const int hqrndm2 = 2147483399;
+        public const int hqrndmagic = 1634357784;
+
+
+        /*************************************************************************
+        HQRNDState  initialization  with  random  values  which come from standard
+        RNG.
+
+          -- ALGLIB --
+             Copyright 02.12.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void hqrndrandomize(hqrndstate state)
+        {
+            hqrndseed(math.randominteger(hqrndm1), math.randominteger(hqrndm2), state);
+        }
+
+
+        /*************************************************************************
+        HQRNDState initialization with seed values
+
+          -- ALGLIB --
+             Copyright 02.12.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void hqrndseed(int s1,
+            int s2,
+            hqrndstate state)
+        {
+            state.s1 = s1%(hqrndm1-1)+1;
+            state.s2 = s2%(hqrndm2-1)+1;
+            state.v = (double)1/(double)hqrndmax;
+            state.magicv = hqrndmagic;
+        }
+
+
+        /*************************************************************************
+        This function generates random real number in (0,1),
+        not including interval boundaries
+
+        State structure must be initialized with HQRNDRandomize() or HQRNDSeed().
+
+          -- ALGLIB --
+             Copyright 02.12.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static double hqrnduniformr(hqrndstate state)
+        {
+            double result = 0;
+
+            result = state.v*hqrndintegerbase(state);
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function generates random integer number in [0, N)
+
+        1. N must be less than HQRNDMax-1.
+        2. State structure must be initialized with HQRNDRandomize() or HQRNDSeed()
+
+          -- ALGLIB --
+             Copyright 02.12.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static int hqrnduniformi(hqrndstate state,
+            int n)
+        {
+            int result = 0;
+            int mx = 0;
+
+            
+            //
+            // Correct handling of N's close to RNDBaseMax
+            // (avoiding skewed distributions for RNDBaseMax<>K*N)
+            //
+            alglib.ap.assert(n>0, "HQRNDUniformI: N<=0!");
+            alglib.ap.assert(n<hqrndmax-1, "HQRNDUniformI: N>=RNDBaseMax-1!");
+            mx = hqrndmax-1-(hqrndmax-1)%n;
+            do
+            {
+                result = hqrndintegerbase(state)-1;
+            }
+            while( result>=mx );
+            result = result%n;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Random number generator: normal numbers
+
+        This function generates one random number from normal distribution.
+        Its performance is equal to that of HQRNDNormal2()
+
+        State structure must be initialized with HQRNDRandomize() or HQRNDSeed().
+
+          -- ALGLIB --
+             Copyright 02.12.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static double hqrndnormal(hqrndstate state)
+        {
+            double result = 0;
+            double v1 = 0;
+            double v2 = 0;
+
+            hqrndnormal2(state, ref v1, ref v2);
+            result = v1;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Random number generator: random X and Y such that X^2+Y^2=1
+
+        State structure must be initialized with HQRNDRandomize() or HQRNDSeed().
+
+          -- ALGLIB --
+             Copyright 02.12.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void hqrndunit2(hqrndstate state,
+            ref double x,
+            ref double y)
+        {
+            double v = 0;
+            double mx = 0;
+            double mn = 0;
+
+            x = 0;
+            y = 0;
+
+            do
+            {
+                hqrndnormal2(state, ref x, ref y);
+            }
+            while( !((double)(x)!=(double)(0) || (double)(y)!=(double)(0)) );
+            mx = Math.Max(Math.Abs(x), Math.Abs(y));
+            mn = Math.Min(Math.Abs(x), Math.Abs(y));
+            v = mx*Math.Sqrt(1+math.sqr(mn/mx));
+            x = x/v;
+            y = y/v;
+        }
+
+
+        /*************************************************************************
+        Random number generator: normal numbers
+
+        This function generates two independent random numbers from normal
+        distribution. Its performance is equal to that of HQRNDNormal()
+
+        State structure must be initialized with HQRNDRandomize() or HQRNDSeed().
+
+          -- ALGLIB --
+             Copyright 02.12.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void hqrndnormal2(hqrndstate state,
+            ref double x1,
+            ref double x2)
+        {
+            double u = 0;
+            double v = 0;
+            double s = 0;
+
+            x1 = 0;
+            x2 = 0;
+
+            while( true )
+            {
+                u = 2*hqrnduniformr(state)-1;
+                v = 2*hqrnduniformr(state)-1;
+                s = math.sqr(u)+math.sqr(v);
+                if( (double)(s)>(double)(0) && (double)(s)<(double)(1) )
+                {
+                    
+                    //
+                    // two Sqrt's instead of one to
+                    // avoid overflow when S is too small
+                    //
+                    s = Math.Sqrt(-(2*Math.Log(s)))/Math.Sqrt(s);
+                    x1 = u*s;
+                    x2 = v*s;
+                    return;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Random number generator: exponential distribution
+
+        State structure must be initialized with HQRNDRandomize() or HQRNDSeed().
+
+          -- ALGLIB --
+             Copyright 11.08.2007 by Bochkanov Sergey
+        *************************************************************************/
+        public static double hqrndexponential(hqrndstate state,
+            double lambdav)
+        {
+            double result = 0;
+
+            alglib.ap.assert((double)(lambdav)>(double)(0), "HQRNDExponential: LambdaV<=0!");
+            result = -(Math.Log(hqrnduniformr(state))/lambdav);
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function generates  random number from discrete distribution given by
+        finite sample X.
+
+        INPUT PARAMETERS
+            State   -   high quality random number generator, must be
+                        initialized with HQRNDRandomize() or HQRNDSeed().
+                X   -   finite sample
+                N   -   number of elements to use, N>=1
+
+        RESULT
+            this function returns one of the X[i] for random i=0..N-1
+
+          -- ALGLIB --
+             Copyright 08.11.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static double hqrnddiscrete(hqrndstate state,
+            double[] x,
+            int n)
+        {
+            double result = 0;
+
+            alglib.ap.assert(n>0, "HQRNDDiscrete: N<=0");
+            alglib.ap.assert(n<=alglib.ap.len(x), "HQRNDDiscrete: Length(X)<N");
+            result = x[hqrnduniformi(state, n)];
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function generates random number from continuous  distribution  given
+        by finite sample X.
+
+        INPUT PARAMETERS
+            State   -   high quality random number generator, must be
+                        initialized with HQRNDRandomize() or HQRNDSeed().
+                X   -   finite sample, array[N] (can be larger, in this  case only
+                        leading N elements are used). THIS ARRAY MUST BE SORTED BY
+                        ASCENDING.
+                N   -   number of elements to use, N>=1
+
+        RESULT
+            this function returns random number from continuous distribution which  
+            tries to approximate X as mush as possible. min(X)<=Result<=max(X).
+
+          -- ALGLIB --
+             Copyright 08.11.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static double hqrndcontinuous(hqrndstate state,
+            double[] x,
+            int n)
+        {
+            double result = 0;
+            double mx = 0;
+            double mn = 0;
+            int i = 0;
+
+            alglib.ap.assert(n>0, "HQRNDContinuous: N<=0");
+            alglib.ap.assert(n<=alglib.ap.len(x), "HQRNDContinuous: Length(X)<N");
+            if( n==1 )
+            {
+                result = x[0];
+                return result;
+            }
+            i = hqrnduniformi(state, n-1);
+            mn = x[i];
+            mx = x[i+1];
+            alglib.ap.assert((double)(mx)>=(double)(mn), "HQRNDDiscrete: X is not sorted by ascending");
+            if( (double)(mx)!=(double)(mn) )
+            {
+                result = (mx-mn)*hqrnduniformr(state)+mn;
+            }
+            else
+            {
+                result = mn;
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+
+        L'Ecuyer, Efficient and portable combined random number generators
+        *************************************************************************/
+        private static int hqrndintegerbase(hqrndstate state)
+        {
+            int result = 0;
+            int k = 0;
+
+            alglib.ap.assert(state.magicv==hqrndmagic, "HQRNDIntegerBase: State is not correctly initialized!");
+            k = state.s1/53668;
+            state.s1 = 40014*(state.s1-k*53668)-k*12211;
+            if( state.s1<0 )
+            {
+                state.s1 = state.s1+2147483563;
+            }
+            k = state.s2/52774;
+            state.s2 = 40692*(state.s2-k*52774)-k*3791;
+            if( state.s2<0 )
+            {
+                state.s2 = state.s2+2147483399;
+            }
+            
+            //
+            // Result
+            //
+            result = state.s1-state.s2;
+            if( result<1 )
+            {
+                result = result+2147483562;
+            }
+            return result;
+        }
+
+
+    }
+    public class nearestneighbor
+    {
+        public class kdtree
+        {
+            public int n;
+            public int nx;
+            public int ny;
+            public int normtype;
+            public double[,] xy;
+            public int[] tags;
+            public double[] boxmin;
+            public double[] boxmax;
+            public int[] nodes;
+            public double[] splits;
+            public double[] x;
+            public int kneeded;
+            public double rneeded;
+            public bool selfmatch;
+            public double approxf;
+            public int kcur;
+            public int[] idx;
+            public double[] r;
+            public double[] buf;
+            public double[] curboxmin;
+            public double[] curboxmax;
+            public double curdist;
+            public int debugcounter;
+            public kdtree()
+            {
+                xy = new double[0,0];
+                tags = new int[0];
+                boxmin = new double[0];
+                boxmax = new double[0];
+                nodes = new int[0];
+                splits = new double[0];
+                x = new double[0];
+                idx = new int[0];
+                r = new double[0];
+                buf = new double[0];
+                curboxmin = new double[0];
+                curboxmax = new double[0];
+            }
+        };
+
+
+
+
+        public const int splitnodesize = 6;
+        public const int kdtreefirstversion = 0;
+
+
+        /*************************************************************************
+        KD-tree creation
+
+        This subroutine creates KD-tree from set of X-values and optional Y-values
+
+        INPUT PARAMETERS
+            XY      -   dataset, array[0..N-1,0..NX+NY-1].
+                        one row corresponds to one point.
+                        first NX columns contain X-values, next NY (NY may be zero)
+                        columns may contain associated Y-values
+            N       -   number of points, N>=0.
+            NX      -   space dimension, NX>=1.
+            NY      -   number of optional Y-values, NY>=0.
+            NormType-   norm type:
+                        * 0 denotes infinity-norm
+                        * 1 denotes 1-norm
+                        * 2 denotes 2-norm (Euclidean norm)
+                        
+        OUTPUT PARAMETERS
+            KDT     -   KD-tree
+            
+            
+        NOTES
+
+        1. KD-tree  creation  have O(N*logN) complexity and O(N*(2*NX+NY))  memory
+           requirements.
+        2. Although KD-trees may be used with any combination of N  and  NX,  they
+           are more efficient than brute-force search only when N >> 4^NX. So they
+           are most useful in low-dimensional tasks (NX=2, NX=3). NX=1  is another
+           inefficient case, because  simple  binary  search  (without  additional
+           structures) is much more efficient in such tasks than KD-trees.
+
+          -- ALGLIB --
+             Copyright 28.02.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void kdtreebuild(double[,] xy,
+            int n,
+            int nx,
+            int ny,
+            int normtype,
+            kdtree kdt)
+        {
+            int[] tags = new int[0];
+            int i = 0;
+
+            alglib.ap.assert(n>=0, "KDTreeBuild: N<0");
+            alglib.ap.assert(nx>=1, "KDTreeBuild: NX<1");
+            alglib.ap.assert(ny>=0, "KDTreeBuild: NY<0");
+            alglib.ap.assert(normtype>=0 && normtype<=2, "KDTreeBuild: incorrect NormType");
+            alglib.ap.assert(alglib.ap.rows(xy)>=n, "KDTreeBuild: rows(X)<N");
+            alglib.ap.assert(alglib.ap.cols(xy)>=nx+ny || n==0, "KDTreeBuild: cols(X)<NX+NY");
+            alglib.ap.assert(apserv.apservisfinitematrix(xy, n, nx+ny), "KDTreeBuild: X contains infinite or NaN values");
+            if( n>0 )
+            {
+                tags = new int[n];
+                for(i=0; i<=n-1; i++)
+                {
+                    tags[i] = 0;
+                }
+            }
+            kdtreebuildtagged(xy, tags, n, nx, ny, normtype, kdt);
+        }
+
+
+        /*************************************************************************
+        KD-tree creation
+
+        This  subroutine  creates  KD-tree  from set of X-values, integer tags and
+        optional Y-values
+
+        INPUT PARAMETERS
+            XY      -   dataset, array[0..N-1,0..NX+NY-1].
+                        one row corresponds to one point.
+                        first NX columns contain X-values, next NY (NY may be zero)
+                        columns may contain associated Y-values
+            Tags    -   tags, array[0..N-1], contains integer tags associated
+                        with points.
+            N       -   number of points, N>=0
+            NX      -   space dimension, NX>=1.
+            NY      -   number of optional Y-values, NY>=0.
+            NormType-   norm type:
+                        * 0 denotes infinity-norm
+                        * 1 denotes 1-norm
+                        * 2 denotes 2-norm (Euclidean norm)
+
+        OUTPUT PARAMETERS
+            KDT     -   KD-tree
+
+        NOTES
+
+        1. KD-tree  creation  have O(N*logN) complexity and O(N*(2*NX+NY))  memory
+           requirements.
+        2. Although KD-trees may be used with any combination of N  and  NX,  they
+           are more efficient than brute-force search only when N >> 4^NX. So they
+           are most useful in low-dimensional tasks (NX=2, NX=3). NX=1  is another
+           inefficient case, because  simple  binary  search  (without  additional
+           structures) is much more efficient in such tasks than KD-trees.
+
+          -- ALGLIB --
+             Copyright 28.02.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void kdtreebuildtagged(double[,] xy,
+            int[] tags,
+            int n,
+            int nx,
+            int ny,
+            int normtype,
+            kdtree kdt)
+        {
+            int i = 0;
+            int j = 0;
+            int maxnodes = 0;
+            int nodesoffs = 0;
+            int splitsoffs = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            alglib.ap.assert(n>=0, "KDTreeBuildTagged: N<0");
+            alglib.ap.assert(nx>=1, "KDTreeBuildTagged: NX<1");
+            alglib.ap.assert(ny>=0, "KDTreeBuildTagged: NY<0");
+            alglib.ap.assert(normtype>=0 && normtype<=2, "KDTreeBuildTagged: incorrect NormType");
+            alglib.ap.assert(alglib.ap.rows(xy)>=n, "KDTreeBuildTagged: rows(X)<N");
+            alglib.ap.assert(alglib.ap.cols(xy)>=nx+ny || n==0, "KDTreeBuildTagged: cols(X)<NX+NY");
+            alglib.ap.assert(apserv.apservisfinitematrix(xy, n, nx+ny), "KDTreeBuildTagged: X contains infinite or NaN values");
+            
+            //
+            // initialize
+            //
+            kdt.n = n;
+            kdt.nx = nx;
+            kdt.ny = ny;
+            kdt.normtype = normtype;
+            kdt.kcur = 0;
+            
+            //
+            // N=0 => quick exit
+            //
+            if( n==0 )
+            {
+                return;
+            }
+            
+            //
+            // Allocate
+            //
+            kdtreeallocdatasetindependent(kdt, nx, ny);
+            kdtreeallocdatasetdependent(kdt, n, nx, ny);
+            
+            //
+            // Initial fill
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                for(i_=0; i_<=nx-1;i_++)
+                {
+                    kdt.xy[i,i_] = xy[i,i_];
+                }
+                i1_ = (0) - (nx);
+                for(i_=nx; i_<=2*nx+ny-1;i_++)
+                {
+                    kdt.xy[i,i_] = xy[i,i_+i1_];
+                }
+                kdt.tags[i] = tags[i];
+            }
+            
+            //
+            // Determine bounding box
+            //
+            for(i_=0; i_<=nx-1;i_++)
+            {
+                kdt.boxmin[i_] = kdt.xy[0,i_];
+            }
+            for(i_=0; i_<=nx-1;i_++)
+            {
+                kdt.boxmax[i_] = kdt.xy[0,i_];
+            }
+            for(i=1; i<=n-1; i++)
+            {
+                for(j=0; j<=nx-1; j++)
+                {
+                    kdt.boxmin[j] = Math.Min(kdt.boxmin[j], kdt.xy[i,j]);
+                    kdt.boxmax[j] = Math.Max(kdt.boxmax[j], kdt.xy[i,j]);
+                }
+            }
+            
+            //
+            // prepare tree structure
+            // * MaxNodes=N because we guarantee no trivial splits, i.e.
+            //   every split will generate two non-empty boxes
+            //
+            maxnodes = n;
+            kdt.nodes = new int[splitnodesize*2*maxnodes];
+            kdt.splits = new double[2*maxnodes];
+            nodesoffs = 0;
+            splitsoffs = 0;
+            for(i_=0; i_<=nx-1;i_++)
+            {
+                kdt.curboxmin[i_] = kdt.boxmin[i_];
+            }
+            for(i_=0; i_<=nx-1;i_++)
+            {
+                kdt.curboxmax[i_] = kdt.boxmax[i_];
+            }
+            kdtreegeneratetreerec(kdt, ref nodesoffs, ref splitsoffs, 0, n, 8);
+        }
+
+
+        /*************************************************************************
+        K-NN query: K nearest neighbors
+
+        INPUT PARAMETERS
+            KDT         -   KD-tree
+            X           -   point, array[0..NX-1].
+            K           -   number of neighbors to return, K>=1
+            SelfMatch   -   whether self-matches are allowed:
+                            * if True, nearest neighbor may be the point itself
+                              (if it exists in original dataset)
+                            * if False, then only points with non-zero distance
+                              are returned
+                            * if not given, considered True
+
+        RESULT
+            number of actual neighbors found (either K or N, if K>N).
+
+        This  subroutine  performs  query  and  stores  its result in the internal
+        structures of the KD-tree. You can use  following  subroutines  to  obtain
+        these results:
+        * KDTreeQueryResultsX() to get X-values
+        * KDTreeQueryResultsXY() to get X- and Y-values
+        * KDTreeQueryResultsTags() to get tag values
+        * KDTreeQueryResultsDistances() to get distances
+
+          -- ALGLIB --
+             Copyright 28.02.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static int kdtreequeryknn(kdtree kdt,
+            double[] x,
+            int k,
+            bool selfmatch)
+        {
+            int result = 0;
+
+            alglib.ap.assert(k>=1, "KDTreeQueryKNN: K<1!");
+            alglib.ap.assert(alglib.ap.len(x)>=kdt.nx, "KDTreeQueryKNN: Length(X)<NX!");
+            alglib.ap.assert(apserv.isfinitevector(x, kdt.nx), "KDTreeQueryKNN: X contains infinite or NaN values!");
+            result = kdtreequeryaknn(kdt, x, k, selfmatch, 0.0);
+            return result;
+        }
+
+
+        /*************************************************************************
+        R-NN query: all points within R-sphere centered at X
+
+        INPUT PARAMETERS
+            KDT         -   KD-tree
+            X           -   point, array[0..NX-1].
+            R           -   radius of sphere (in corresponding norm), R>0
+            SelfMatch   -   whether self-matches are allowed:
+                            * if True, nearest neighbor may be the point itself
+                              (if it exists in original dataset)
+                            * if False, then only points with non-zero distance
+                              are returned
+                            * if not given, considered True
+
+        RESULT
+            number of neighbors found, >=0
+
+        This  subroutine  performs  query  and  stores  its result in the internal
+        structures of the KD-tree. You can use  following  subroutines  to  obtain
+        actual results:
+        * KDTreeQueryResultsX() to get X-values
+        * KDTreeQueryResultsXY() to get X- and Y-values
+        * KDTreeQueryResultsTags() to get tag values
+        * KDTreeQueryResultsDistances() to get distances
+
+          -- ALGLIB --
+             Copyright 28.02.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static int kdtreequeryrnn(kdtree kdt,
+            double[] x,
+            double r,
+            bool selfmatch)
+        {
+            int result = 0;
+            int i = 0;
+            int j = 0;
+
+            alglib.ap.assert((double)(r)>(double)(0), "KDTreeQueryRNN: incorrect R!");
+            alglib.ap.assert(alglib.ap.len(x)>=kdt.nx, "KDTreeQueryRNN: Length(X)<NX!");
+            alglib.ap.assert(apserv.isfinitevector(x, kdt.nx), "KDTreeQueryRNN: X contains infinite or NaN values!");
+            
+            //
+            // Handle special case: KDT.N=0
+            //
+            if( kdt.n==0 )
+            {
+                kdt.kcur = 0;
+                result = 0;
+                return result;
+            }
+            
+            //
+            // Prepare parameters
+            //
+            kdt.kneeded = 0;
+            if( kdt.normtype!=2 )
+            {
+                kdt.rneeded = r;
+            }
+            else
+            {
+                kdt.rneeded = math.sqr(r);
+            }
+            kdt.selfmatch = selfmatch;
+            kdt.approxf = 1;
+            kdt.kcur = 0;
+            
+            //
+            // calculate distance from point to current bounding box
+            //
+            kdtreeinitbox(kdt, x);
+            
+            //
+            // call recursive search
+            // results are returned as heap
+            //
+            kdtreequerynnrec(kdt, 0);
+            
+            //
+            // pop from heap to generate ordered representation
+            //
+            // last element is not pop'ed because it is already in
+            // its place
+            //
+            result = kdt.kcur;
+            j = kdt.kcur;
+            for(i=kdt.kcur; i>=2; i--)
+            {
+                tsort.tagheappopi(ref kdt.r, ref kdt.idx, ref j);
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        K-NN query: approximate K nearest neighbors
+
+        INPUT PARAMETERS
+            KDT         -   KD-tree
+            X           -   point, array[0..NX-1].
+            K           -   number of neighbors to return, K>=1
+            SelfMatch   -   whether self-matches are allowed:
+                            * if True, nearest neighbor may be the point itself
+                              (if it exists in original dataset)
+                            * if False, then only points with non-zero distance
+                              are returned
+                            * if not given, considered True
+            Eps         -   approximation factor, Eps>=0. eps-approximate  nearest
+                            neighbor  is  a  neighbor  whose distance from X is at
+                            most (1+eps) times distance of true nearest neighbor.
+
+        RESULT
+            number of actual neighbors found (either K or N, if K>N).
+            
+        NOTES
+            significant performance gain may be achieved only when Eps  is  is  on
+            the order of magnitude of 1 or larger.
+
+        This  subroutine  performs  query  and  stores  its result in the internal
+        structures of the KD-tree. You can use  following  subroutines  to  obtain
+        these results:
+        * KDTreeQueryResultsX() to get X-values
+        * KDTreeQueryResultsXY() to get X- and Y-values
+        * KDTreeQueryResultsTags() to get tag values
+        * KDTreeQueryResultsDistances() to get distances
+
+          -- ALGLIB --
+             Copyright 28.02.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static int kdtreequeryaknn(kdtree kdt,
+            double[] x,
+            int k,
+            bool selfmatch,
+            double eps)
+        {
+            int result = 0;
+            int i = 0;
+            int j = 0;
+
+            alglib.ap.assert(k>0, "KDTreeQueryAKNN: incorrect K!");
+            alglib.ap.assert((double)(eps)>=(double)(0), "KDTreeQueryAKNN: incorrect Eps!");
+            alglib.ap.assert(alglib.ap.len(x)>=kdt.nx, "KDTreeQueryAKNN: Length(X)<NX!");
+            alglib.ap.assert(apserv.isfinitevector(x, kdt.nx), "KDTreeQueryAKNN: X contains infinite or NaN values!");
+            
+            //
+            // Handle special case: KDT.N=0
+            //
+            if( kdt.n==0 )
+            {
+                kdt.kcur = 0;
+                result = 0;
+                return result;
+            }
+            
+            //
+            // Prepare parameters
+            //
+            k = Math.Min(k, kdt.n);
+            kdt.kneeded = k;
+            kdt.rneeded = 0;
+            kdt.selfmatch = selfmatch;
+            if( kdt.normtype==2 )
+            {
+                kdt.approxf = 1/math.sqr(1+eps);
+            }
+            else
+            {
+                kdt.approxf = 1/(1+eps);
+            }
+            kdt.kcur = 0;
+            
+            //
+            // calculate distance from point to current bounding box
+            //
+            kdtreeinitbox(kdt, x);
+            
+            //
+            // call recursive search
+            // results are returned as heap
+            //
+            kdtreequerynnrec(kdt, 0);
+            
+            //
+            // pop from heap to generate ordered representation
+            //
+            // last element is non pop'ed because it is already in
+            // its place
+            //
+            result = kdt.kcur;
+            j = kdt.kcur;
+            for(i=kdt.kcur; i>=2; i--)
+            {
+                tsort.tagheappopi(ref kdt.r, ref kdt.idx, ref j);
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        X-values from last query
+
+        INPUT PARAMETERS
+            KDT     -   KD-tree
+            X       -   possibly pre-allocated buffer. If X is too small to store
+                        result, it is resized. If size(X) is enough to store
+                        result, it is left unchanged.
+
+        OUTPUT PARAMETERS
+            X       -   rows are filled with X-values
+
+        NOTES
+        1. points are ordered by distance from the query point (first = closest)
+        2. if  XY is larger than required to store result, only leading part  will
+           be overwritten; trailing part will be left unchanged. So  if  on  input
+           XY = [[A,B],[C,D]], and result is [1,2],  then  on  exit  we  will  get
+           XY = [[1,2],[C,D]]. This is done purposely to increase performance;  if
+           you want function  to  resize  array  according  to  result  size,  use
+           function with same name and suffix 'I'.
+
+        SEE ALSO
+        * KDTreeQueryResultsXY()            X- and Y-values
+        * KDTreeQueryResultsTags()          tag values
+        * KDTreeQueryResultsDistances()     distances
+
+          -- ALGLIB --
+             Copyright 28.02.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void kdtreequeryresultsx(kdtree kdt,
+            ref double[,] x)
+        {
+            int i = 0;
+            int k = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            if( kdt.kcur==0 )
+            {
+                return;
+            }
+            if( alglib.ap.rows(x)<kdt.kcur || alglib.ap.cols(x)<kdt.nx )
+            {
+                x = new double[kdt.kcur, kdt.nx];
+            }
+            k = kdt.kcur;
+            for(i=0; i<=k-1; i++)
+            {
+                i1_ = (kdt.nx) - (0);
+                for(i_=0; i_<=kdt.nx-1;i_++)
+                {
+                    x[i,i_] = kdt.xy[kdt.idx[i],i_+i1_];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        X- and Y-values from last query
+
+        INPUT PARAMETERS
+            KDT     -   KD-tree
+            XY      -   possibly pre-allocated buffer. If XY is too small to store
+                        result, it is resized. If size(XY) is enough to store
+                        result, it is left unchanged.
+
+        OUTPUT PARAMETERS
+            XY      -   rows are filled with points: first NX columns with
+                        X-values, next NY columns - with Y-values.
+
+        NOTES
+        1. points are ordered by distance from the query point (first = closest)
+        2. if  XY is larger than required to store result, only leading part  will
+           be overwritten; trailing part will be left unchanged. So  if  on  input
+           XY = [[A,B],[C,D]], and result is [1,2],  then  on  exit  we  will  get
+           XY = [[1,2],[C,D]]. This is done purposely to increase performance;  if
+           you want function  to  resize  array  according  to  result  size,  use
+           function with same name and suffix 'I'.
+
+        SEE ALSO
+        * KDTreeQueryResultsX()             X-values
+        * KDTreeQueryResultsTags()          tag values
+        * KDTreeQueryResultsDistances()     distances
+
+          -- ALGLIB --
+             Copyright 28.02.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void kdtreequeryresultsxy(kdtree kdt,
+            ref double[,] xy)
+        {
+            int i = 0;
+            int k = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            if( kdt.kcur==0 )
+            {
+                return;
+            }
+            if( alglib.ap.rows(xy)<kdt.kcur || alglib.ap.cols(xy)<kdt.nx+kdt.ny )
+            {
+                xy = new double[kdt.kcur, kdt.nx+kdt.ny];
+            }
+            k = kdt.kcur;
+            for(i=0; i<=k-1; i++)
+            {
+                i1_ = (kdt.nx) - (0);
+                for(i_=0; i_<=kdt.nx+kdt.ny-1;i_++)
+                {
+                    xy[i,i_] = kdt.xy[kdt.idx[i],i_+i1_];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Tags from last query
+
+        INPUT PARAMETERS
+            KDT     -   KD-tree
+            Tags    -   possibly pre-allocated buffer. If X is too small to store
+                        result, it is resized. If size(X) is enough to store
+                        result, it is left unchanged.
+
+        OUTPUT PARAMETERS
+            Tags    -   filled with tags associated with points,
+                        or, when no tags were supplied, with zeros
+
+        NOTES
+        1. points are ordered by distance from the query point (first = closest)
+        2. if  XY is larger than required to store result, only leading part  will
+           be overwritten; trailing part will be left unchanged. So  if  on  input
+           XY = [[A,B],[C,D]], and result is [1,2],  then  on  exit  we  will  get
+           XY = [[1,2],[C,D]]. This is done purposely to increase performance;  if
+           you want function  to  resize  array  according  to  result  size,  use
+           function with same name and suffix 'I'.
+
+        SEE ALSO
+        * KDTreeQueryResultsX()             X-values
+        * KDTreeQueryResultsXY()            X- and Y-values
+        * KDTreeQueryResultsDistances()     distances
+
+          -- ALGLIB --
+             Copyright 28.02.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void kdtreequeryresultstags(kdtree kdt,
+            ref int[] tags)
+        {
+            int i = 0;
+            int k = 0;
+
+            if( kdt.kcur==0 )
+            {
+                return;
+            }
+            if( alglib.ap.len(tags)<kdt.kcur )
+            {
+                tags = new int[kdt.kcur];
+            }
+            k = kdt.kcur;
+            for(i=0; i<=k-1; i++)
+            {
+                tags[i] = kdt.tags[kdt.idx[i]];
+            }
+        }
+
+
+        /*************************************************************************
+        Distances from last query
+
+        INPUT PARAMETERS
+            KDT     -   KD-tree
+            R       -   possibly pre-allocated buffer. If X is too small to store
+                        result, it is resized. If size(X) is enough to store
+                        result, it is left unchanged.
+
+        OUTPUT PARAMETERS
+            R       -   filled with distances (in corresponding norm)
+
+        NOTES
+        1. points are ordered by distance from the query point (first = closest)
+        2. if  XY is larger than required to store result, only leading part  will
+           be overwritten; trailing part will be left unchanged. So  if  on  input
+           XY = [[A,B],[C,D]], and result is [1,2],  then  on  exit  we  will  get
+           XY = [[1,2],[C,D]]. This is done purposely to increase performance;  if
+           you want function  to  resize  array  according  to  result  size,  use
+           function with same name and suffix 'I'.
+
+        SEE ALSO
+        * KDTreeQueryResultsX()             X-values
+        * KDTreeQueryResultsXY()            X- and Y-values
+        * KDTreeQueryResultsTags()          tag values
+
+          -- ALGLIB --
+             Copyright 28.02.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void kdtreequeryresultsdistances(kdtree kdt,
+            ref double[] r)
+        {
+            int i = 0;
+            int k = 0;
+
+            if( kdt.kcur==0 )
+            {
+                return;
+            }
+            if( alglib.ap.len(r)<kdt.kcur )
+            {
+                r = new double[kdt.kcur];
+            }
+            k = kdt.kcur;
+            
+            //
+            // unload norms
+            //
+            // Abs() call is used to handle cases with negative norms
+            // (generated during KFN requests)
+            //
+            if( kdt.normtype==0 )
+            {
+                for(i=0; i<=k-1; i++)
+                {
+                    r[i] = Math.Abs(kdt.r[i]);
+                }
+            }
+            if( kdt.normtype==1 )
+            {
+                for(i=0; i<=k-1; i++)
+                {
+                    r[i] = Math.Abs(kdt.r[i]);
+                }
+            }
+            if( kdt.normtype==2 )
+            {
+                for(i=0; i<=k-1; i++)
+                {
+                    r[i] = Math.Sqrt(Math.Abs(kdt.r[i]));
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        X-values from last query; 'interactive' variant for languages like  Python
+        which   support    constructs   like  "X = KDTreeQueryResultsXI(KDT)"  and
+        interactive mode of interpreter.
+
+        This function allocates new array on each call,  so  it  is  significantly
+        slower than its 'non-interactive' counterpart, but it is  more  convenient
+        when you call it from command line.
+
+          -- ALGLIB --
+             Copyright 28.02.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void kdtreequeryresultsxi(kdtree kdt,
+            ref double[,] x)
+        {
+            x = new double[0,0];
+
+            kdtreequeryresultsx(kdt, ref x);
+        }
+
+
+        /*************************************************************************
+        XY-values from last query; 'interactive' variant for languages like Python
+        which   support    constructs   like "XY = KDTreeQueryResultsXYI(KDT)" and
+        interactive mode of interpreter.
+
+        This function allocates new array on each call,  so  it  is  significantly
+        slower than its 'non-interactive' counterpart, but it is  more  convenient
+        when you call it from command line.
+
+          -- ALGLIB --
+             Copyright 28.02.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void kdtreequeryresultsxyi(kdtree kdt,
+            ref double[,] xy)
+        {
+            xy = new double[0,0];
+
+            kdtreequeryresultsxy(kdt, ref xy);
+        }
+
+
+        /*************************************************************************
+        Tags  from  last  query;  'interactive' variant for languages like  Python
+        which  support  constructs  like "Tags = KDTreeQueryResultsTagsI(KDT)" and
+        interactive mode of interpreter.
+
+        This function allocates new array on each call,  so  it  is  significantly
+        slower than its 'non-interactive' counterpart, but it is  more  convenient
+        when you call it from command line.
+
+          -- ALGLIB --
+             Copyright 28.02.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void kdtreequeryresultstagsi(kdtree kdt,
+            ref int[] tags)
+        {
+            tags = new int[0];
+
+            kdtreequeryresultstags(kdt, ref tags);
+        }
+
+
+        /*************************************************************************
+        Distances from last query; 'interactive' variant for languages like Python
+        which  support  constructs   like  "R = KDTreeQueryResultsDistancesI(KDT)"
+        and interactive mode of interpreter.
+
+        This function allocates new array on each call,  so  it  is  significantly
+        slower than its 'non-interactive' counterpart, but it is  more  convenient
+        when you call it from command line.
+
+          -- ALGLIB --
+             Copyright 28.02.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void kdtreequeryresultsdistancesi(kdtree kdt,
+            ref double[] r)
+        {
+            r = new double[0];
+
+            kdtreequeryresultsdistances(kdt, ref r);
+        }
+
+
+        /*************************************************************************
+        Serializer: allocation
+
+          -- ALGLIB --
+             Copyright 14.03.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void kdtreealloc(alglib.serializer s,
+            kdtree tree)
+        {
+            
+            //
+            // Header
+            //
+            s.alloc_entry();
+            s.alloc_entry();
+            
+            //
+            // Data
+            //
+            s.alloc_entry();
+            s.alloc_entry();
+            s.alloc_entry();
+            s.alloc_entry();
+            apserv.allocrealmatrix(s, tree.xy, -1, -1);
+            apserv.allocintegerarray(s, tree.tags, -1);
+            apserv.allocrealarray(s, tree.boxmin, -1);
+            apserv.allocrealarray(s, tree.boxmax, -1);
+            apserv.allocintegerarray(s, tree.nodes, -1);
+            apserv.allocrealarray(s, tree.splits, -1);
+        }
+
+
+        /*************************************************************************
+        Serializer: serialization
+
+          -- ALGLIB --
+             Copyright 14.03.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void kdtreeserialize(alglib.serializer s,
+            kdtree tree)
+        {
+            
+            //
+            // Header
+            //
+            s.serialize_int(scodes.getkdtreeserializationcode());
+            s.serialize_int(kdtreefirstversion);
+            
+            //
+            // Data
+            //
+            s.serialize_int(tree.n);
+            s.serialize_int(tree.nx);
+            s.serialize_int(tree.ny);
+            s.serialize_int(tree.normtype);
+            apserv.serializerealmatrix(s, tree.xy, -1, -1);
+            apserv.serializeintegerarray(s, tree.tags, -1);
+            apserv.serializerealarray(s, tree.boxmin, -1);
+            apserv.serializerealarray(s, tree.boxmax, -1);
+            apserv.serializeintegerarray(s, tree.nodes, -1);
+            apserv.serializerealarray(s, tree.splits, -1);
+        }
+
+
+        /*************************************************************************
+        Serializer: unserialization
+
+          -- ALGLIB --
+             Copyright 14.03.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void kdtreeunserialize(alglib.serializer s,
+            kdtree tree)
+        {
+            int i0 = 0;
+            int i1 = 0;
+
+            
+            //
+            // check correctness of header
+            //
+            i0 = s.unserialize_int();
+            alglib.ap.assert(i0==scodes.getkdtreeserializationcode(), "KDTreeUnserialize: stream header corrupted");
+            i1 = s.unserialize_int();
+            alglib.ap.assert(i1==kdtreefirstversion, "KDTreeUnserialize: stream header corrupted");
+            
+            //
+            // Unserialize data
+            //
+            tree.n = s.unserialize_int();
+            tree.nx = s.unserialize_int();
+            tree.ny = s.unserialize_int();
+            tree.normtype = s.unserialize_int();
+            apserv.unserializerealmatrix(s, ref tree.xy);
+            apserv.unserializeintegerarray(s, ref tree.tags);
+            apserv.unserializerealarray(s, ref tree.boxmin);
+            apserv.unserializerealarray(s, ref tree.boxmax);
+            apserv.unserializeintegerarray(s, ref tree.nodes);
+            apserv.unserializerealarray(s, ref tree.splits);
+            kdtreealloctemporaries(tree, tree.n, tree.nx, tree.ny);
+        }
+
+
+        /*************************************************************************
+        Rearranges nodes [I1,I2) using partition in D-th dimension with S as threshold.
+        Returns split position I3: [I1,I3) and [I3,I2) are created as result.
+
+        This subroutine doesn't create tree structures, just rearranges nodes.
+        *************************************************************************/
+        private static void kdtreesplit(kdtree kdt,
+            int i1,
+            int i2,
+            int d,
+            double s,
+            ref int i3)
+        {
+            int i = 0;
+            int j = 0;
+            int ileft = 0;
+            int iright = 0;
+            double v = 0;
+
+            i3 = 0;
+
+            alglib.ap.assert(kdt.n>0, "KDTreeSplit: internal error");
+            
+            //
+            // split XY/Tags in two parts:
+            // * [ILeft,IRight] is non-processed part of XY/Tags
+            //
+            // After cycle is done, we have Ileft=IRight. We deal with
+            // this element separately.
+            //
+            // After this, [I1,ILeft) contains left part, and [ILeft,I2)
+            // contains right part.
+            //
+            ileft = i1;
+            iright = i2-1;
+            while( ileft<iright )
+            {
+                if( (double)(kdt.xy[ileft,d])<=(double)(s) )
+                {
+                    
+                    //
+                    // XY[ILeft] is on its place.
+                    // Advance ILeft.
+                    //
+                    ileft = ileft+1;
+                }
+                else
+                {
+                    
+                    //
+                    // XY[ILeft,..] must be at IRight.
+                    // Swap and advance IRight.
+                    //
+                    for(i=0; i<=2*kdt.nx+kdt.ny-1; i++)
+                    {
+                        v = kdt.xy[ileft,i];
+                        kdt.xy[ileft,i] = kdt.xy[iright,i];
+                        kdt.xy[iright,i] = v;
+                    }
+                    j = kdt.tags[ileft];
+                    kdt.tags[ileft] = kdt.tags[iright];
+                    kdt.tags[iright] = j;
+                    iright = iright-1;
+                }
+            }
+            if( (double)(kdt.xy[ileft,d])<=(double)(s) )
+            {
+                ileft = ileft+1;
+            }
+            else
+            {
+                iright = iright-1;
+            }
+            i3 = ileft;
+        }
+
+
+        /*************************************************************************
+        Recursive kd-tree generation subroutine.
+
+        PARAMETERS
+            KDT         tree
+            NodesOffs   unused part of Nodes[] which must be filled by tree
+            SplitsOffs  unused part of Splits[]
+            I1, I2      points from [I1,I2) are processed
+            
+        NodesOffs[] and SplitsOffs[] must be large enough.
+
+          -- ALGLIB --
+             Copyright 28.02.2010 by Bochkanov Sergey
+        *************************************************************************/
+        private static void kdtreegeneratetreerec(kdtree kdt,
+            ref int nodesoffs,
+            ref int splitsoffs,
+            int i1,
+            int i2,
+            int maxleafsize)
+        {
+            int n = 0;
+            int nx = 0;
+            int ny = 0;
+            int i = 0;
+            int j = 0;
+            int oldoffs = 0;
+            int i3 = 0;
+            int cntless = 0;
+            int cntgreater = 0;
+            double minv = 0;
+            double maxv = 0;
+            int minidx = 0;
+            int maxidx = 0;
+            int d = 0;
+            double ds = 0;
+            double s = 0;
+            double v = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            alglib.ap.assert(kdt.n>0, "KDTreeGenerateTreeRec: internal error");
+            alglib.ap.assert(i2>i1, "KDTreeGenerateTreeRec: internal error");
+            
+            //
+            // Generate leaf if needed
+            //
+            if( i2-i1<=maxleafsize )
+            {
+                kdt.nodes[nodesoffs+0] = i2-i1;
+                kdt.nodes[nodesoffs+1] = i1;
+                nodesoffs = nodesoffs+2;
+                return;
+            }
+            
+            //
+            // Load values for easier access
+            //
+            nx = kdt.nx;
+            ny = kdt.ny;
+            
+            //
+            // select dimension to split:
+            // * D is a dimension number
+            //
+            d = 0;
+            ds = kdt.curboxmax[0]-kdt.curboxmin[0];
+            for(i=1; i<=nx-1; i++)
+            {
+                v = kdt.curboxmax[i]-kdt.curboxmin[i];
+                if( (double)(v)>(double)(ds) )
+                {
+                    ds = v;
+                    d = i;
+                }
+            }
+            
+            //
+            // Select split position S using sliding midpoint rule,
+            // rearrange points into [I1,I3) and [I3,I2)
+            //
+            s = kdt.curboxmin[d]+0.5*ds;
+            i1_ = (i1) - (0);
+            for(i_=0; i_<=i2-i1-1;i_++)
+            {
+                kdt.buf[i_] = kdt.xy[i_+i1_,d];
+            }
+            n = i2-i1;
+            cntless = 0;
+            cntgreater = 0;
+            minv = kdt.buf[0];
+            maxv = kdt.buf[0];
+            minidx = i1;
+            maxidx = i1;
+            for(i=0; i<=n-1; i++)
+            {
+                v = kdt.buf[i];
+                if( (double)(v)<(double)(minv) )
+                {
+                    minv = v;
+                    minidx = i1+i;
+                }
+                if( (double)(v)>(double)(maxv) )
+                {
+                    maxv = v;
+                    maxidx = i1+i;
+                }
+                if( (double)(v)<(double)(s) )
+                {
+                    cntless = cntless+1;
+                }
+                if( (double)(v)>(double)(s) )
+                {
+                    cntgreater = cntgreater+1;
+                }
+            }
+            if( cntless>0 && cntgreater>0 )
+            {
+                
+                //
+                // normal midpoint split
+                //
+                kdtreesplit(kdt, i1, i2, d, s, ref i3);
+            }
+            else
+            {
+                
+                //
+                // sliding midpoint
+                //
+                if( cntless==0 )
+                {
+                    
+                    //
+                    // 1. move split to MinV,
+                    // 2. place one point to the left bin (move to I1),
+                    //    others - to the right bin
+                    //
+                    s = minv;
+                    if( minidx!=i1 )
+                    {
+                        for(i=0; i<=2*kdt.nx+kdt.ny-1; i++)
+                        {
+                            v = kdt.xy[minidx,i];
+                            kdt.xy[minidx,i] = kdt.xy[i1,i];
+                            kdt.xy[i1,i] = v;
+                        }
+                        j = kdt.tags[minidx];
+                        kdt.tags[minidx] = kdt.tags[i1];
+                        kdt.tags[i1] = j;
+                    }
+                    i3 = i1+1;
+                }
+                else
+                {
+                    
+                    //
+                    // 1. move split to MaxV,
+                    // 2. place one point to the right bin (move to I2-1),
+                    //    others - to the left bin
+                    //
+                    s = maxv;
+                    if( maxidx!=i2-1 )
+                    {
+                        for(i=0; i<=2*kdt.nx+kdt.ny-1; i++)
+                        {
+                            v = kdt.xy[maxidx,i];
+                            kdt.xy[maxidx,i] = kdt.xy[i2-1,i];
+                            kdt.xy[i2-1,i] = v;
+                        }
+                        j = kdt.tags[maxidx];
+                        kdt.tags[maxidx] = kdt.tags[i2-1];
+                        kdt.tags[i2-1] = j;
+                    }
+                    i3 = i2-1;
+                }
+            }
+            
+            //
+            // Generate 'split' node
+            //
+            kdt.nodes[nodesoffs+0] = 0;
+            kdt.nodes[nodesoffs+1] = d;
+            kdt.nodes[nodesoffs+2] = splitsoffs;
+            kdt.splits[splitsoffs+0] = s;
+            oldoffs = nodesoffs;
+            nodesoffs = nodesoffs+splitnodesize;
+            splitsoffs = splitsoffs+1;
+            
+            //
+            // Recirsive generation:
+            // * update CurBox
+            // * call subroutine
+            // * restore CurBox
+            //
+            kdt.nodes[oldoffs+3] = nodesoffs;
+            v = kdt.curboxmax[d];
+            kdt.curboxmax[d] = s;
+            kdtreegeneratetreerec(kdt, ref nodesoffs, ref splitsoffs, i1, i3, maxleafsize);
+            kdt.curboxmax[d] = v;
+            kdt.nodes[oldoffs+4] = nodesoffs;
+            v = kdt.curboxmin[d];
+            kdt.curboxmin[d] = s;
+            kdtreegeneratetreerec(kdt, ref nodesoffs, ref splitsoffs, i3, i2, maxleafsize);
+            kdt.curboxmin[d] = v;
+        }
+
+
+        /*************************************************************************
+        Recursive subroutine for NN queries.
+
+          -- ALGLIB --
+             Copyright 28.02.2010 by Bochkanov Sergey
+        *************************************************************************/
+        private static void kdtreequerynnrec(kdtree kdt,
+            int offs)
+        {
+            double ptdist = 0;
+            int i = 0;
+            int j = 0;
+            int nx = 0;
+            int i1 = 0;
+            int i2 = 0;
+            int d = 0;
+            double s = 0;
+            double v = 0;
+            double t1 = 0;
+            int childbestoffs = 0;
+            int childworstoffs = 0;
+            int childoffs = 0;
+            double prevdist = 0;
+            bool todive = new bool();
+            bool bestisleft = new bool();
+            bool updatemin = new bool();
+
+            alglib.ap.assert(kdt.n>0, "KDTreeQueryNNRec: internal error");
+            
+            //
+            // Leaf node.
+            // Process points.
+            //
+            if( kdt.nodes[offs]>0 )
+            {
+                i1 = kdt.nodes[offs+1];
+                i2 = i1+kdt.nodes[offs];
+                for(i=i1; i<=i2-1; i++)
+                {
+                    
+                    //
+                    // Calculate distance
+                    //
+                    ptdist = 0;
+                    nx = kdt.nx;
+                    if( kdt.normtype==0 )
+                    {
+                        for(j=0; j<=nx-1; j++)
+                        {
+                            ptdist = Math.Max(ptdist, Math.Abs(kdt.xy[i,j]-kdt.x[j]));
+                        }
+                    }
+                    if( kdt.normtype==1 )
+                    {
+                        for(j=0; j<=nx-1; j++)
+                        {
+                            ptdist = ptdist+Math.Abs(kdt.xy[i,j]-kdt.x[j]);
+                        }
+                    }
+                    if( kdt.normtype==2 )
+                    {
+                        for(j=0; j<=nx-1; j++)
+                        {
+                            ptdist = ptdist+math.sqr(kdt.xy[i,j]-kdt.x[j]);
+                        }
+                    }
+                    
+                    //
+                    // Skip points with zero distance if self-matches are turned off
+                    //
+                    if( (double)(ptdist)==(double)(0) && !kdt.selfmatch )
+                    {
+                        continue;
+                    }
+                    
+                    //
+                    // We CAN'T process point if R-criterion isn't satisfied,
+                    // i.e. (RNeeded<>0) AND (PtDist>R).
+                    //
+                    if( (double)(kdt.rneeded)==(double)(0) || (double)(ptdist)<=(double)(kdt.rneeded) )
+                    {
+                        
+                        //
+                        // R-criterion is satisfied, we must either:
+                        // * replace worst point, if (KNeeded<>0) AND (KCur=KNeeded)
+                        //   (or skip, if worst point is better)
+                        // * add point without replacement otherwise
+                        //
+                        if( kdt.kcur<kdt.kneeded || kdt.kneeded==0 )
+                        {
+                            
+                            //
+                            // add current point to heap without replacement
+                            //
+                            tsort.tagheappushi(ref kdt.r, ref kdt.idx, ref kdt.kcur, ptdist, i);
+                        }
+                        else
+                        {
+                            
+                            //
+                            // New points are added or not, depending on their distance.
+                            // If added, they replace element at the top of the heap
+                            //
+                            if( (double)(ptdist)<(double)(kdt.r[0]) )
+                            {
+                                if( kdt.kneeded==1 )
+                                {
+                                    kdt.idx[0] = i;
+                                    kdt.r[0] = ptdist;
+                                }
+                                else
+                                {
+                                    tsort.tagheapreplacetopi(ref kdt.r, ref kdt.idx, kdt.kneeded, ptdist, i);
+                                }
+                            }
+                        }
+                    }
+                }
+                return;
+            }
+            
+            //
+            // Simple split
+            //
+            if( kdt.nodes[offs]==0 )
+            {
+                
+                //
+                // Load:
+                // * D  dimension to split
+                // * S  split position
+                //
+                d = kdt.nodes[offs+1];
+                s = kdt.splits[kdt.nodes[offs+2]];
+                
+                //
+                // Calculate:
+                // * ChildBestOffs      child box with best chances
+                // * ChildWorstOffs     child box with worst chances
+                //
+                if( (double)(kdt.x[d])<=(double)(s) )
+                {
+                    childbestoffs = kdt.nodes[offs+3];
+                    childworstoffs = kdt.nodes[offs+4];
+                    bestisleft = true;
+                }
+                else
+                {
+                    childbestoffs = kdt.nodes[offs+4];
+                    childworstoffs = kdt.nodes[offs+3];
+                    bestisleft = false;
+                }
+                
+                //
+                // Navigate through childs
+                //
+                for(i=0; i<=1; i++)
+                {
+                    
+                    //
+                    // Select child to process:
+                    // * ChildOffs      current child offset in Nodes[]
+                    // * UpdateMin      whether minimum or maximum value
+                    //                  of bounding box is changed on update
+                    //
+                    if( i==0 )
+                    {
+                        childoffs = childbestoffs;
+                        updatemin = !bestisleft;
+                    }
+                    else
+                    {
+                        updatemin = bestisleft;
+                        childoffs = childworstoffs;
+                    }
+                    
+                    //
+                    // Update bounding box and current distance
+                    //
+                    if( updatemin )
+                    {
+                        prevdist = kdt.curdist;
+                        t1 = kdt.x[d];
+                        v = kdt.curboxmin[d];
+                        if( (double)(t1)<=(double)(s) )
+                        {
+                            if( kdt.normtype==0 )
+                            {
+                                kdt.curdist = Math.Max(kdt.curdist, s-t1);
+                            }
+                            if( kdt.normtype==1 )
+                            {
+                                kdt.curdist = kdt.curdist-Math.Max(v-t1, 0)+s-t1;
+                            }
+                            if( kdt.normtype==2 )
+                            {
+                                kdt.curdist = kdt.curdist-math.sqr(Math.Max(v-t1, 0))+math.sqr(s-t1);
+                            }
+                        }
+                        kdt.curboxmin[d] = s;
+                    }
+                    else
+                    {
+                        prevdist = kdt.curdist;
+                        t1 = kdt.x[d];
+                        v = kdt.curboxmax[d];
+                        if( (double)(t1)>=(double)(s) )
+                        {
+                            if( kdt.normtype==0 )
+                            {
+                                kdt.curdist = Math.Max(kdt.curdist, t1-s);
+                            }
+                            if( kdt.normtype==1 )
+                            {
+                                kdt.curdist = kdt.curdist-Math.Max(t1-v, 0)+t1-s;
+                            }
+                            if( kdt.normtype==2 )
+                            {
+                                kdt.curdist = kdt.curdist-math.sqr(Math.Max(t1-v, 0))+math.sqr(t1-s);
+                            }
+                        }
+                        kdt.curboxmax[d] = s;
+                    }
+                    
+                    //
+                    // Decide: to dive into cell or not to dive
+                    //
+                    if( (double)(kdt.rneeded)!=(double)(0) && (double)(kdt.curdist)>(double)(kdt.rneeded) )
+                    {
+                        todive = false;
+                    }
+                    else
+                    {
+                        if( kdt.kcur<kdt.kneeded || kdt.kneeded==0 )
+                        {
+                            
+                            //
+                            // KCur<KNeeded (i.e. not all points are found)
+                            //
+                            todive = true;
+                        }
+                        else
+                        {
+                            
+                            //
+                            // KCur=KNeeded, decide to dive or not to dive
+                            // using point position relative to bounding box.
+                            //
+                            todive = (double)(kdt.curdist)<=(double)(kdt.r[0]*kdt.approxf);
+                        }
+                    }
+                    if( todive )
+                    {
+                        kdtreequerynnrec(kdt, childoffs);
+                    }
+                    
+                    //
+                    // Restore bounding box and distance
+                    //
+                    if( updatemin )
+                    {
+                        kdt.curboxmin[d] = v;
+                    }
+                    else
+                    {
+                        kdt.curboxmax[d] = v;
+                    }
+                    kdt.curdist = prevdist;
+                }
+                return;
+            }
+        }
+
+
+        /*************************************************************************
+        Copies X[] to KDT.X[]
+        Loads distance from X[] to bounding box.
+        Initializes CurBox[].
+
+          -- ALGLIB --
+             Copyright 28.02.2010 by Bochkanov Sergey
+        *************************************************************************/
+        private static void kdtreeinitbox(kdtree kdt,
+            double[] x)
+        {
+            int i = 0;
+            double vx = 0;
+            double vmin = 0;
+            double vmax = 0;
+
+            alglib.ap.assert(kdt.n>0, "KDTreeInitBox: internal error");
+            
+            //
+            // calculate distance from point to current bounding box
+            //
+            kdt.curdist = 0;
+            if( kdt.normtype==0 )
+            {
+                for(i=0; i<=kdt.nx-1; i++)
+                {
+                    vx = x[i];
+                    vmin = kdt.boxmin[i];
+                    vmax = kdt.boxmax[i];
+                    kdt.x[i] = vx;
+                    kdt.curboxmin[i] = vmin;
+                    kdt.curboxmax[i] = vmax;
+                    if( (double)(vx)<(double)(vmin) )
+                    {
+                        kdt.curdist = Math.Max(kdt.curdist, vmin-vx);
+                    }
+                    else
+                    {
+                        if( (double)(vx)>(double)(vmax) )
+                        {
+                            kdt.curdist = Math.Max(kdt.curdist, vx-vmax);
+                        }
+                    }
+                }
+            }
+            if( kdt.normtype==1 )
+            {
+                for(i=0; i<=kdt.nx-1; i++)
+                {
+                    vx = x[i];
+                    vmin = kdt.boxmin[i];
+                    vmax = kdt.boxmax[i];
+                    kdt.x[i] = vx;
+                    kdt.curboxmin[i] = vmin;
+                    kdt.curboxmax[i] = vmax;
+                    if( (double)(vx)<(double)(vmin) )
+                    {
+                        kdt.curdist = kdt.curdist+vmin-vx;
+                    }
+                    else
+                    {
+                        if( (double)(vx)>(double)(vmax) )
+                        {
+                            kdt.curdist = kdt.curdist+vx-vmax;
+                        }
+                    }
+                }
+            }
+            if( kdt.normtype==2 )
+            {
+                for(i=0; i<=kdt.nx-1; i++)
+                {
+                    vx = x[i];
+                    vmin = kdt.boxmin[i];
+                    vmax = kdt.boxmax[i];
+                    kdt.x[i] = vx;
+                    kdt.curboxmin[i] = vmin;
+                    kdt.curboxmax[i] = vmax;
+                    if( (double)(vx)<(double)(vmin) )
+                    {
+                        kdt.curdist = kdt.curdist+math.sqr(vmin-vx);
+                    }
+                    else
+                    {
+                        if( (double)(vx)>(double)(vmax) )
+                        {
+                            kdt.curdist = kdt.curdist+math.sqr(vx-vmax);
+                        }
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This function allocates all dataset-independent array  fields  of  KDTree,
+        i.e.  such  array  fields  that  their dimensions do not depend on dataset
+        size.
+
+        This function do not sets KDT.NX or KDT.NY - it just allocates arrays
+
+          -- ALGLIB --
+             Copyright 14.03.2011 by Bochkanov Sergey
+        *************************************************************************/
+        private static void kdtreeallocdatasetindependent(kdtree kdt,
+            int nx,
+            int ny)
+        {
+            alglib.ap.assert(kdt.n>0, "KDTreeAllocDatasetIndependent: internal error");
+            kdt.x = new double[nx];
+            kdt.boxmin = new double[nx];
+            kdt.boxmax = new double[nx];
+            kdt.curboxmin = new double[nx];
+            kdt.curboxmax = new double[nx];
+        }
+
+
+        /*************************************************************************
+        This function allocates all dataset-dependent array fields of KDTree, i.e.
+        such array fields that their dimensions depend on dataset size.
+
+        This function do not sets KDT.N, KDT.NX or KDT.NY -
+        it just allocates arrays.
+
+          -- ALGLIB --
+             Copyright 14.03.2011 by Bochkanov Sergey
+        *************************************************************************/
+        private static void kdtreeallocdatasetdependent(kdtree kdt,
+            int n,
+            int nx,
+            int ny)
+        {
+            alglib.ap.assert(n>0, "KDTreeAllocDatasetDependent: internal error");
+            kdt.xy = new double[n, 2*nx+ny];
+            kdt.tags = new int[n];
+            kdt.idx = new int[n];
+            kdt.r = new double[n];
+            kdt.x = new double[nx];
+            kdt.buf = new double[Math.Max(n, nx)];
+            kdt.nodes = new int[splitnodesize*2*n];
+            kdt.splits = new double[2*n];
+        }
+
+
+        /*************************************************************************
+        This function allocates temporaries.
+
+        This function do not sets KDT.N, KDT.NX or KDT.NY -
+        it just allocates arrays.
+
+          -- ALGLIB --
+             Copyright 14.03.2011 by Bochkanov Sergey
+        *************************************************************************/
+        private static void kdtreealloctemporaries(kdtree kdt,
+            int n,
+            int nx,
+            int ny)
+        {
+            alglib.ap.assert(n>0, "KDTreeAllocTemporaries: internal error");
+            kdt.x = new double[nx];
+            kdt.idx = new int[n];
+            kdt.r = new double[n];
+            kdt.buf = new double[Math.Max(n, nx)];
+            kdt.curboxmin = new double[nx];
+            kdt.curboxmax = new double[nx];
+        }
+
+
+    }
+}
+
Index: /trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.5.0/ALGLIB-3.5.0/ap.cs
===================================================================
--- /trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.5.0/ALGLIB-3.5.0/ap.cs	(revision 7673)
+++ /trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.5.0/ALGLIB-3.5.0/ap.cs	(revision 7673)
@@ -0,0 +1,1224 @@
+/*************************************************************************
+AP library
+Copyright (c) 2003-2009 Sergey Bochkanov (ALGLIB project).
+
+>>> SOURCE LICENSE >>>
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation (www.fsf.org); either version 2 of the
+License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+A copy of the GNU General Public License is available at
+http://www.fsf.org/licensing/licenses
+>>> END OF LICENSE >>>
+*************************************************************************/
+using System;
+public partial class alglib
+{
+    /********************************************************************
+    Callback definitions for optimizers/fitters/solvers.
+    
+    Callbacks for unparameterized (general) functions:
+    * ndimensional_func         calculates f(arg), stores result to func
+    * ndimensional_grad         calculates func = f(arg), 
+                                grad[i] = df(arg)/d(arg[i])
+    * ndimensional_hess         calculates func = f(arg),
+                                grad[i] = df(arg)/d(arg[i]),
+                                hess[i,j] = d2f(arg)/(d(arg[i])*d(arg[j]))
+    
+    Callbacks for systems of functions:
+    * ndimensional_fvec         calculates vector function f(arg),
+                                stores result to fi
+    * ndimensional_jac          calculates f[i] = fi(arg)
+                                jac[i,j] = df[i](arg)/d(arg[j])
+                                
+    Callbacks for  parameterized  functions,  i.e.  for  functions  which 
+    depend on two vectors: P and Q.  Gradient  and Hessian are calculated 
+    with respect to P only.
+    * ndimensional_pfunc        calculates f(p,q),
+                                stores result to func
+    * ndimensional_pgrad        calculates func = f(p,q),
+                                grad[i] = df(p,q)/d(p[i])
+    * ndimensional_phess        calculates func = f(p,q),
+                                grad[i] = df(p,q)/d(p[i]),
+                                hess[i,j] = d2f(p,q)/(d(p[i])*d(p[j]))
+
+    Callbacks for progress reports:
+    * ndimensional_rep          reports current position of optimization algo    
+    
+    Callbacks for ODE solvers:
+    * ndimensional_ode_rp       calculates dy/dx for given y[] and x
+    
+    Callbacks for integrators:
+    * integrator1_func          calculates f(x) for given x
+                                (additional parameters xminusa and bminusx
+                                contain x-a and b-x)
+    ********************************************************************/
+    public delegate void ndimensional_func (double[] arg, ref double func, object obj);
+    public delegate void ndimensional_grad (double[] arg, ref double func, double[] grad, object obj);
+    public delegate void ndimensional_hess (double[] arg, ref double func, double[] grad, double[,] hess, object obj);
+    
+    public delegate void ndimensional_fvec (double[] arg, double[] fi, object obj);
+    public delegate void ndimensional_jac  (double[] arg, double[] fi, double[,] jac, object obj);
+    
+    public delegate void ndimensional_pfunc(double[] p, double[] q, ref double func, object obj);
+    public delegate void ndimensional_pgrad(double[] p, double[] q, ref double func, double[] grad, object obj);
+    public delegate void ndimensional_phess(double[] p, double[] q, ref double func, double[] grad, double[,] hess, object obj);
+    
+    public delegate void ndimensional_rep(double[] arg, double func, object obj);
+
+    public delegate void ndimensional_ode_rp (double[] y, double x, double[] dy, object obj);
+
+    public delegate void integrator1_func (double x, double xminusa, double bminusx, ref double f, object obj);
+
+    /********************************************************************
+    Class defining a complex number with double precision.
+    ********************************************************************/
+    public struct complex
+    {
+        public double x;
+        public double y;
+
+        public complex(double _x)
+        {
+            x = _x;
+            y = 0;
+        }
+        public complex(double _x, double _y)
+        {
+            x = _x;
+            y = _y;
+        }
+        public static implicit operator complex(double _x)
+        {
+            return new complex(_x);
+        }
+        public static bool operator==(complex lhs, complex rhs)
+        {
+            return ((double)lhs.x==(double)rhs.x) & ((double)lhs.y==(double)rhs.y);
+        }
+        public static bool operator!=(complex lhs, complex rhs)
+        {
+            return ((double)lhs.x!=(double)rhs.x) | ((double)lhs.y!=(double)rhs.y);
+        }
+        public static complex operator+(complex lhs)
+        {
+            return lhs;
+        }
+        public static complex operator-(complex lhs)
+        {
+            return new complex(-lhs.x,-lhs.y);
+        }
+        public static complex operator+(complex lhs, complex rhs)
+        {
+            return new complex(lhs.x+rhs.x,lhs.y+rhs.y);
+        }
+        public static complex operator-(complex lhs, complex rhs)
+        {
+            return new complex(lhs.x-rhs.x,lhs.y-rhs.y);
+        }
+        public static complex operator*(complex lhs, complex rhs)
+        { 
+            return new complex(lhs.x*rhs.x-lhs.y*rhs.y, lhs.x*rhs.y+lhs.y*rhs.x);
+        }
+        public static complex operator/(complex lhs, complex rhs)
+        {
+            complex result;
+            double e;
+            double f;
+            if( System.Math.Abs(rhs.y)<System.Math.Abs(rhs.x) )
+            {
+                e = rhs.y/rhs.x;
+                f = rhs.x+rhs.y*e;
+                result.x = (lhs.x+lhs.y*e)/f;
+                result.y = (lhs.y-lhs.x*e)/f;
+            }
+            else
+            {
+                e = rhs.x/rhs.y;
+                f = rhs.y+rhs.x*e;
+                result.x = (lhs.y+lhs.x*e)/f;
+                result.y = (-lhs.x+lhs.y*e)/f;
+            }
+            return result;
+        }
+        public override int GetHashCode() 
+        { 
+            return x.GetHashCode() ^ y.GetHashCode(); 
+        }
+        public override bool Equals(object obj) 
+        { 
+            if( obj is byte)
+                return Equals(new complex((byte)obj));
+            if( obj is sbyte)
+                return Equals(new complex((sbyte)obj));
+            if( obj is short)
+                return Equals(new complex((short)obj));
+            if( obj is ushort)
+                return Equals(new complex((ushort)obj));
+            if( obj is int)
+                return Equals(new complex((int)obj));
+            if( obj is uint)
+                return Equals(new complex((uint)obj));
+            if( obj is long)
+                return Equals(new complex((long)obj));
+            if( obj is ulong)
+                return Equals(new complex((ulong)obj));
+            if( obj is float)
+                return Equals(new complex((float)obj));
+            if( obj is double)
+                return Equals(new complex((double)obj));
+            if( obj is decimal)
+                return Equals(new complex((double)(decimal)obj));
+            return base.Equals(obj); 
+        }    
+    }    
+    
+    /********************************************************************
+    Class defining an ALGLIB exception
+    ********************************************************************/
+    public class alglibexception : System.Exception
+    {
+        public string msg;
+        public alglibexception(string s)
+        {
+            msg = s;
+        }
+        
+    }
+    
+    /********************************************************************
+    reverse communication structure
+    ********************************************************************/
+    public class rcommstate
+    {
+        public rcommstate()
+        {
+            stage = -1;
+            ia = new int[0];
+            ba = new bool[0];
+            ra = new double[0];
+            ca = new alglib.complex[0];
+        }
+        public int stage;
+        public int[] ia;
+        public bool[] ba;
+        public double[] ra;
+        public alglib.complex[] ca;
+    };
+
+    /********************************************************************
+    internal functions
+    ********************************************************************/
+    public class ap
+    {
+        public static int len<T>(T[] a)
+        { return a.Length; }
+        public static int rows<T>(T[,] a)
+        { return a.GetLength(0); }
+        public static int cols<T>(T[,] a)
+        { return a.GetLength(1); }
+        public static void swap<T>(ref T a, ref T b)
+        {
+            T t = a;
+            a = b;
+            b = t;
+        }
+        
+        public static void assert(bool cond, string s)
+        {
+            if( !cond )
+                throw new alglibexception(s);
+        }
+        
+        public static void assert(bool cond)
+        {
+            assert(cond, "ALGLIB: assertion failed");
+        }
+        
+        /****************************************************************
+        returns dps (digits-of-precision) value corresponding to threshold.
+        dps(0.9)  = dps(0.5)  = dps(0.1) = 0
+        dps(0.09) = dps(0.05) = dps(0.01) = 1
+        and so on
+        ****************************************************************/
+        public static int threshold2dps(double threshold)
+        {
+            int result = 0;
+            double t;
+            for (result = 0, t = 1; t / 10 > threshold*(1+1E-10); result++, t /= 10) ;
+            return result;
+        }
+
+        /****************************************************************
+        prints formatted complex
+        ****************************************************************/
+        public static string format(complex a, int _dps)
+        {
+            int dps = Math.Abs(_dps);
+            string fmt = _dps>=0 ? "F" : "E";
+            string fmtx = String.Format("{{0:"+fmt+"{0}}}", dps);
+            string fmty = String.Format("{{0:"+fmt+"{0}}}", dps);
+            string result = String.Format(fmtx, a.x) + (a.y >= 0 ? "+" : "-") + String.Format(fmty, Math.Abs(a.y)) + "i";
+            result = result.Replace(',', '.');
+            return result;
+        }
+
+        /****************************************************************
+        prints formatted array
+        ****************************************************************/
+        public static string format(bool[] a)
+        {
+            string[] result = new string[len(a)];
+            int i;
+            for(i=0; i<len(a); i++)
+                if( a[i] )
+                    result[i] = "true";
+                else
+                    result[i] = "false";
+            return "{"+String.Join(",",result)+"}";
+        }
+        
+        /****************************************************************
+        prints formatted array
+        ****************************************************************/
+        public static string format(int[] a)
+        {
+            string[] result = new string[len(a)];
+            int i;
+            for (i = 0; i < len(a); i++)
+                result[i] = a[i].ToString();
+            return "{" + String.Join(",", result) + "}";
+        }
+
+        /****************************************************************
+        prints formatted array
+        ****************************************************************/
+        public static string format(double[] a, int _dps)
+        {
+            int dps = Math.Abs(_dps);
+            string sfmt = _dps >= 0 ? "F" : "E";
+            string fmt = String.Format("{{0:" + sfmt + "{0}}}", dps);
+            string[] result = new string[len(a)];
+            int i;
+            for (i = 0; i < len(a); i++)
+            {
+                result[i] = String.Format(fmt, a[i]);
+                result[i] = result[i].Replace(',', '.');
+            }
+            return "{" + String.Join(",", result) + "}";
+        }
+
+        /****************************************************************
+        prints formatted array
+        ****************************************************************/
+        public static string format(complex[] a, int _dps)
+        {
+            int dps = Math.Abs(_dps);
+            string fmt = _dps >= 0 ? "F" : "E";
+            string fmtx = String.Format("{{0:"+fmt+"{0}}}", dps);
+            string fmty = String.Format("{{0:"+fmt+"{0}}}", dps);
+            string[] result = new string[len(a)];
+            int i;
+            for (i = 0; i < len(a); i++)
+            {
+                result[i] = String.Format(fmtx, a[i].x) + (a[i].y >= 0 ? "+" : "-") + String.Format(fmty, Math.Abs(a[i].y)) + "i";
+                result[i] = result[i].Replace(',', '.');
+            }
+            return "{" + String.Join(",", result) + "}";
+        }
+
+        /****************************************************************
+        prints formatted matrix
+        ****************************************************************/
+        public static string format(bool[,] a)
+        {
+            int i, j, m, n;
+            n = cols(a);
+            m = rows(a);
+            bool[] line = new bool[n];
+            string[] result = new string[m];
+            for (i = 0; i < m; i++)
+            {
+                for (j = 0; j < n; j++)
+                    line[j] = a[i, j];
+                result[i] = format(line);
+            }
+            return "{" + String.Join(",", result) + "}";
+        }
+
+        /****************************************************************
+        prints formatted matrix
+        ****************************************************************/
+        public static string format(int[,] a)
+        {
+            int i, j, m, n;
+            n = cols(a);
+            m = rows(a);
+            int[] line = new int[n];
+            string[] result = new string[m];
+            for (i = 0; i < m; i++)
+            {
+                for (j = 0; j < n; j++)
+                    line[j] = a[i, j];
+                result[i] = format(line);
+            }
+            return "{" + String.Join(",", result) + "}";
+        }
+
+        /****************************************************************
+        prints formatted matrix
+        ****************************************************************/
+        public static string format(double[,] a, int dps)
+        {
+            int i, j, m, n;
+            n = cols(a);
+            m = rows(a);
+            double[] line = new double[n];
+            string[] result = new string[m];
+            for (i = 0; i < m; i++)
+            {
+                for (j = 0; j < n; j++)
+                    line[j] = a[i, j];
+                result[i] = format(line, dps);
+            }
+            return "{" + String.Join(",", result) + "}";
+        }
+
+        /****************************************************************
+        prints formatted matrix
+        ****************************************************************/
+        public static string format(complex[,] a, int dps)
+        {
+            int i, j, m, n;
+            n = cols(a);
+            m = rows(a);
+            complex[] line = new complex[n];
+            string[] result = new string[m];
+            for (i = 0; i < m; i++)
+            {
+                for (j = 0; j < n; j++)
+                    line[j] = a[i, j];
+                result[i] = format(line, dps);
+            }
+            return "{" + String.Join(",", result) + "}";
+        }
+
+        /****************************************************************
+        checks that matrix is symmetric.
+        max|A-A^T| is calculated; if it is within 1.0E-14 of max|A|,
+        matrix is considered symmetric
+        ****************************************************************/
+        public static bool issymmetric(double[,] a)
+        {
+            int i, j, n;
+            double err, mx, v1, v2;
+            if( rows(a)!=cols(a) )
+                return false;
+            n = rows(a);
+            if( n==0 )
+                return true;
+            mx = 0;
+            err = 0;
+            for( i=0; i<n; i++)
+            {
+                for(j=i+1; j<n; j++)
+                {
+                    v1 = a[i,j];
+                    v2 = a[j,i];
+                    if( !math.isfinite(v1) )
+                        return false;
+                    if( !math.isfinite(v2) )
+                        return false;
+                    err = Math.Max(err, Math.Abs(v1-v2));
+                    mx  = Math.Max(mx,  Math.Abs(v1));
+                    mx  = Math.Max(mx,  Math.Abs(v2));
+                }
+                v1 = a[i,i];
+                if( !math.isfinite(v1) )
+                    return false;
+                mx = Math.Max(mx, Math.Abs(v1));
+            }
+            if( mx==0 )
+                return true;
+            return err/mx<=1.0E-14;
+        }
+        
+        /****************************************************************
+        checks that matrix is Hermitian.
+        max|A-A^H| is calculated; if it is within 1.0E-14 of max|A|,
+        matrix is considered Hermitian
+        ****************************************************************/
+        public static bool ishermitian(complex[,] a)
+        {
+            int i, j, n;
+            double err, mx;
+            complex v1, v2, vt;
+            if( rows(a)!=cols(a) )
+                return false;
+            n = rows(a);
+            if( n==0 )
+                return true;
+            mx = 0;
+            err = 0;
+            for( i=0; i<n; i++)
+            {
+                for(j=i+1; j<n; j++)
+                {
+                    v1 = a[i,j];
+                    v2 = a[j,i];
+                    if( !math.isfinite(v1.x) )
+                        return false;
+                    if( !math.isfinite(v1.y) )
+                        return false;
+                    if( !math.isfinite(v2.x) )
+                        return false;
+                    if( !math.isfinite(v2.y) )
+                        return false;
+                    vt.x = v1.x-v2.x;
+                    vt.y = v1.y+v2.y;
+                    err = Math.Max(err, math.abscomplex(vt));
+                    mx  = Math.Max(mx,  math.abscomplex(v1));
+                    mx  = Math.Max(mx,  math.abscomplex(v2));
+                }
+                v1 = a[i,i];
+                if( !math.isfinite(v1.x) )
+                    return false;
+                if( !math.isfinite(v1.y) )
+                    return false;
+                err = Math.Max(err, Math.Abs(v1.y));
+                mx = Math.Max(mx, math.abscomplex(v1));
+            }
+            if( mx==0 )
+                return true;
+            return err/mx<=1.0E-14;
+        }
+        
+        
+        /****************************************************************
+        Forces symmetricity by copying upper half of A to the lower one
+        ****************************************************************/
+        public static bool forcesymmetric(double[,] a)
+        {
+            int i, j, n;
+            if( rows(a)!=cols(a) )
+                return false;
+            n = rows(a);
+            if( n==0 )
+                return true;
+            for( i=0; i<n; i++)
+                for(j=i+1; j<n; j++)
+                    a[i,j] = a[j,i];
+            return true;
+        }
+        
+        /****************************************************************
+        Forces Hermiticity by copying upper half of A to the lower one
+        ****************************************************************/
+        public static bool forcehermitian(complex[,] a)
+        {
+            int i, j, n;
+            complex v;
+            if( rows(a)!=cols(a) )
+                return false;
+            n = rows(a);
+            if( n==0 )
+                return true;
+            for( i=0; i<n; i++)
+                for(j=i+1; j<n; j++)
+                {
+                    v = a[j,i];
+                    a[i,j].x = v.x;
+                    a[i,j].y = -v.y;
+                }
+            return true;
+        }
+    };
+    
+    /********************************************************************
+    math functions
+    ********************************************************************/
+    public class math
+    {
+        //public static System.Random RndObject = new System.Random(System.DateTime.Now.Millisecond);
+        public static System.Random rndobject = new System.Random(System.DateTime.Now.Millisecond + 1000*System.DateTime.Now.Second + 60*1000*System.DateTime.Now.Minute);
+
+        public const double machineepsilon = 5E-16;
+        public const double maxrealnumber = 1E300;
+        public const double minrealnumber = 1E-300;
+        
+        public static bool isfinite(double d)
+        {
+            return !System.Double.IsNaN(d) && !System.Double.IsInfinity(d);
+        }
+        
+        public static double randomreal()
+        {
+            double r = 0;
+            lock(rndobject){ r = rndobject.NextDouble(); }
+            return r;
+        }
+        public static int randominteger(int N)
+        {
+            int r = 0;
+            lock(rndobject){ r = rndobject.Next(N); }
+            return r;
+        }
+        public static double sqr(double X)
+        {
+            return X*X;
+        }        
+        public static double abscomplex(complex z)
+        {
+            double w;
+            double xabs;
+            double yabs;
+            double v;
+    
+            xabs = System.Math.Abs(z.x);
+            yabs = System.Math.Abs(z.y);
+            w = xabs>yabs ? xabs : yabs;
+            v = xabs<yabs ? xabs : yabs; 
+            if( v==0 )
+                return w;
+            else
+            {
+                double t = v/w;
+                return w*System.Math.Sqrt(1+t*t);
+            }
+        }
+        public static complex conj(complex z)
+        {
+            return new complex(z.x, -z.y); 
+        }    
+        public static complex csqr(complex z)
+        {
+            return new complex(z.x*z.x-z.y*z.y, 2*z.x*z.y); 
+        }
+
+    }
+    
+    /********************************************************************
+    serializer object (should not be used directly)
+    ********************************************************************/
+    public class serializer
+    {
+        enum SMODE { DEFAULT, ALLOC, TO_STRING, FROM_STRING };
+        private const int SER_ENTRIES_PER_ROW = 5;
+        private const int SER_ENTRY_LENGTH    = 11;
+        
+        private SMODE mode;
+        private int entries_needed;
+        private int entries_saved;
+        private int bytes_asked;
+        private int bytes_written;
+        private int bytes_read;
+        private char[] out_str;
+        private char[] in_str;
+        
+        public serializer()
+        {
+            mode = SMODE.DEFAULT;
+            entries_needed = 0;
+            bytes_asked = 0;
+        }
+
+        public void alloc_start()
+        {
+            entries_needed = 0;
+            bytes_asked = 0;
+            mode = SMODE.ALLOC;
+        }
+
+        public void alloc_entry()
+        {
+            if( mode!=SMODE.ALLOC )
+                throw new alglib.alglibexception("ALGLIB: internal error during (un)serialization");
+            entries_needed++;
+        }
+
+        private int get_alloc_size()
+        {
+            int rows, lastrowsize, result;
+            
+            // check and change mode
+            if( mode!=SMODE.ALLOC )
+                throw new alglib.alglibexception("ALGLIB: internal error during (un)serialization");
+            
+            // if no entries needes (degenerate case)
+            if( entries_needed==0 )
+            {
+                bytes_asked = 1;
+                return bytes_asked;
+            }
+            
+            // non-degenerate case
+            rows = entries_needed/SER_ENTRIES_PER_ROW;
+            lastrowsize = SER_ENTRIES_PER_ROW;
+            if( entries_needed%SER_ENTRIES_PER_ROW!=0 )
+            {
+                lastrowsize = entries_needed%SER_ENTRIES_PER_ROW;
+                rows++;
+            }
+            
+            // calculate result size
+            result  = ((rows-1)*SER_ENTRIES_PER_ROW+lastrowsize)*SER_ENTRY_LENGTH;
+            result +=  (rows-1)*(SER_ENTRIES_PER_ROW-1)+(lastrowsize-1);
+            result += rows*2;
+            bytes_asked = result;
+            return result;
+        }
+
+        public void sstart_str()
+        {
+            int allocsize = get_alloc_size();
+            
+            // check and change mode
+            if( mode!=SMODE.ALLOC )
+                throw new alglib.alglibexception("ALGLIB: internal error during (un)serialization");
+            mode = SMODE.TO_STRING;
+            
+            // other preparations
+            out_str = new char[allocsize];
+            entries_saved = 0;
+            bytes_written = 0;
+        }
+
+        public void ustart_str(string s)
+        {
+            // check and change mode
+            if( mode!=SMODE.DEFAULT )
+                throw new alglib.alglibexception("ALGLIB: internal error during (un)serialization");
+            mode = SMODE.FROM_STRING;
+            
+            in_str = s.ToCharArray();
+            bytes_read = 0;
+        }
+
+        public void serialize_bool(bool v)
+        {
+            if( mode!=SMODE.TO_STRING )
+                throw new alglib.alglibexception("ALGLIB: internal error during (un)serialization");
+            bool2str(v, out_str, ref bytes_written);
+            entries_saved++;
+            if( entries_saved%SER_ENTRIES_PER_ROW!=0 )
+            {
+                out_str[bytes_written] = ' ';
+                bytes_written++;
+            }
+            else
+            {
+                out_str[bytes_written+0] = '\r';
+                out_str[bytes_written+1] = '\n';
+                bytes_written+=2;
+            }            
+        }
+
+        public void serialize_int(int v)
+        {
+            if( mode!=SMODE.TO_STRING )
+                throw new alglib.alglibexception("ALGLIB: internal error during (un)serialization");
+            int2str(v, out_str, ref bytes_written);
+            entries_saved++;
+            if( entries_saved%SER_ENTRIES_PER_ROW!=0 )
+            {
+                out_str[bytes_written] = ' ';
+                bytes_written++;
+            }
+            else
+            {
+                out_str[bytes_written+0] = '\r';
+                out_str[bytes_written+1] = '\n';
+                bytes_written+=2;
+            }
+        }
+
+        public void serialize_double(double v)
+        {
+            if( mode!=SMODE.TO_STRING )
+                throw new alglib.alglibexception("ALGLIB: internal error during (un)serialization");
+            double2str(v, out_str, ref bytes_written);
+            entries_saved++;
+            if( entries_saved%SER_ENTRIES_PER_ROW!=0 )
+            {
+                out_str[bytes_written] = ' ';
+                bytes_written++;
+            }
+            else
+            {
+                out_str[bytes_written+0] = '\r';
+                out_str[bytes_written+1] = '\n';
+                bytes_written+=2;
+            }
+        }
+
+        public bool unserialize_bool()
+        {
+            if( mode!=SMODE.FROM_STRING )
+                throw new alglib.alglibexception("ALGLIB: internal error during (un)serialization");
+            return str2bool(in_str, ref bytes_read);
+        }
+
+        public int unserialize_int()
+        {
+            if( mode!=SMODE.FROM_STRING )
+                throw new alglib.alglibexception("ALGLIB: internal error during (un)serialization");
+            return str2int(in_str, ref bytes_read);
+        }
+
+        public double unserialize_double()
+        {
+            if( mode!=SMODE.FROM_STRING )
+                throw new alglib.alglibexception("ALGLIB: internal error during (un)serialization");
+            return str2double(in_str, ref bytes_read);
+        }
+
+        public void stop()
+        {
+        }
+
+        public string get_string()
+        {
+            return new string(out_str, 0, bytes_written);
+        }
+
+
+        /************************************************************************
+        This function converts six-bit value (from 0 to 63)  to  character  (only
+        digits, lowercase and uppercase letters, minus and underscore are used).
+
+        If v is negative or greater than 63, this function returns '?'.
+        ************************************************************************/
+        private static char[] _sixbits2char_tbl = new char[64]{ 
+                '0', '1', '2', '3', '4', '5', '6', '7',
+                '8', '9', 'A', 'B', 'C', 'D', 'E', 'F',
+                'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
+                'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
+                'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 
+                'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 
+                'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 
+                'u', 'v', 'w', 'x', 'y', 'z', '-', '_' };
+        private static char sixbits2char(int v)
+        {
+            if( v<0 || v>63 )
+                return '?';
+            return _sixbits2char_tbl[v];
+        }
+        
+        /************************************************************************
+        This function converts character to six-bit value (from 0 to 63).
+
+        This function is inverse of ae_sixbits2char()
+        If c is not correct character, this function returns -1.
+        ************************************************************************/
+        private static int[] _char2sixbits_tbl = new int[128] {
+            -1, -1, -1, -1, -1, -1, -1, -1,
+            -1, -1, -1, -1, -1, -1, -1, -1,
+            -1, -1, -1, -1, -1, -1, -1, -1,
+            -1, -1, -1, -1, -1, -1, -1, -1,
+            -1, -1, -1, -1, -1, -1, -1, -1,
+            -1, -1, -1, -1, -1, 62, -1, -1,
+             0,  1,  2,  3,  4,  5,  6,  7,
+             8,  9, -1, -1, -1, -1, -1, -1,
+            -1, 10, 11, 12, 13, 14, 15, 16,
+            17, 18, 19, 20, 21, 22, 23, 24,
+            25, 26, 27, 28, 29, 30, 31, 32,
+            33, 34, 35, -1, -1, -1, -1, 63,
+            -1, 36, 37, 38, 39, 40, 41, 42,
+            43, 44, 45, 46, 47, 48, 49, 50,
+            51, 52, 53, 54, 55, 56, 57, 58,
+            59, 60, 61, -1, -1, -1, -1, -1 };
+        private static int char2sixbits(char c)
+        {
+            return (c>=0 && c<127) ? _char2sixbits_tbl[c] : -1;
+        }
+        
+        /************************************************************************
+        This function converts three bytes (24 bits) to four six-bit values 
+        (24 bits again).
+
+        src         array
+        src_offs    offset of three-bytes chunk
+        dst         array for ints
+        dst_offs    offset of four-ints chunk
+        ************************************************************************/
+        private static void threebytes2foursixbits(byte[] src, int src_offs, int[] dst, int dst_offs)
+        {
+            dst[dst_offs+0] =  src[src_offs+0] & 0x3F;
+            dst[dst_offs+1] = (src[src_offs+0]>>6) | ((src[src_offs+1]&0x0F)<<2);
+            dst[dst_offs+2] = (src[src_offs+1]>>4) | ((src[src_offs+2]&0x03)<<4);
+            dst[dst_offs+3] =  src[src_offs+2]>>2;
+        }
+
+        /************************************************************************
+        This function converts four six-bit values (24 bits) to three bytes
+        (24 bits again).
+
+        src         pointer to four ints
+        src_offs    offset of the chunk
+        dst         pointer to three bytes
+        dst_offs    offset of the chunk
+        ************************************************************************/
+        private static void foursixbits2threebytes(int[] src, int src_offs, byte[] dst, int dst_offs)
+        {
+            dst[dst_offs+0] =      (byte)(src[src_offs+0] | ((src[src_offs+1]&0x03)<<6));
+            dst[dst_offs+1] = (byte)((src[src_offs+1]>>2) | ((src[src_offs+2]&0x0F)<<4));
+            dst[dst_offs+2] = (byte)((src[src_offs+2]>>4) |  (src[src_offs+3]<<2));
+        }
+
+        /************************************************************************
+        This function serializes boolean value into buffer
+
+        v           boolean value to be serialized
+        buf         buffer, at least 11 characters wide
+        offs        offset in the buffer
+        
+        after return from this function, offs points to the char's past the value
+        being read.
+        ************************************************************************/
+        private static void bool2str(bool v, char[] buf, ref int offs)
+        {
+            char c = v ? '1' : '0';
+            int i;
+            for(i=0; i<SER_ENTRY_LENGTH; i++)
+                buf[offs+i] = c;
+            offs += SER_ENTRY_LENGTH;
+        }
+
+        /************************************************************************
+        This function unserializes boolean value from buffer
+
+        buf         buffer which contains value; leading spaces/tabs/newlines are 
+                    ignored, traling spaces/tabs/newlines are treated as  end  of
+                    the boolean value.
+        offs        offset in the buffer
+        
+        after return from this function, offs points to the char's past the value
+        being read.
+
+        This function raises an error in case unexpected symbol is found
+        ************************************************************************/
+        private static bool str2bool(char[] buf, ref int offs)
+        {
+            bool was0, was1;
+            string emsg = "ALGLIB: unable to read boolean value from stream";
+            
+            was0 = false;
+            was1 = false;
+            while( buf[offs]==' ' || buf[offs]=='\t' || buf[offs]=='\n' || buf[offs]=='\r' )
+                offs++;
+            while( buf[offs]!=' ' && buf[offs]!='\t' && buf[offs]!='\n' && buf[offs]!='\r' && buf[offs]!=0 )
+            {
+                if( buf[offs]=='0' )
+                {
+                    was0 = true;
+                    offs++;
+                    continue;
+                }
+                if( buf[offs]=='1' )
+                {
+                    was1 = true;
+                    offs++;
+                    continue;
+                }
+                throw new alglib.alglibexception(emsg);
+            }
+            if( (!was0) && (!was1) )
+                throw new alglib.alglibexception(emsg);
+            if( was0 && was1 )
+                throw new alglib.alglibexception(emsg);
+            return was1 ? true : false;
+        }
+
+        /************************************************************************
+        This function serializes integer value into buffer
+
+        v           integer value to be serialized
+        buf         buffer, at least 11 characters wide 
+        offs        offset in the buffer
+        
+        after return from this function, offs points to the char's past the value
+        being read.
+
+        This function raises an error in case unexpected symbol is found
+        ************************************************************************/
+        private static void int2str(int v, char[] buf, ref int offs)
+        {
+            int i;
+            byte[] _bytes = System.BitConverter.GetBytes((int)v);
+            byte[]  bytes = new byte[9];
+            int[] sixbits = new int[12];
+            byte c;
+            
+            //
+            // copy v to array of bytes, sign extending it and 
+            // converting to little endian order. Additionally, 
+            // we set 9th byte to zero in order to simplify 
+            // conversion to six-bit representation
+            //
+            if( !System.BitConverter.IsLittleEndian )
+                System.Array.Reverse(_bytes);
+            c = v<0 ? (byte)0xFF : (byte)0x00;
+            for(i=0; i<sizeof(int); i++)
+                bytes[i] = _bytes[i];
+            for(i=sizeof(int); i<8; i++)
+                bytes[i] = c;
+            bytes[8] = 0;
+            
+            //
+            // convert to six-bit representation, output
+            //
+            // NOTE: last 12th element of sixbits is always zero, we do not output it
+            //
+            threebytes2foursixbits(bytes, 0, sixbits, 0);
+            threebytes2foursixbits(bytes, 3, sixbits, 4);
+            threebytes2foursixbits(bytes, 6, sixbits, 8);        
+            for(i=0; i<SER_ENTRY_LENGTH; i++)
+                buf[offs+i] = sixbits2char(sixbits[i]);
+            offs += SER_ENTRY_LENGTH;
+        }
+
+        /************************************************************************
+        This function unserializes integer value from string
+
+        buf         buffer which contains value; leading spaces/tabs/newlines are 
+                    ignored, traling spaces/tabs/newlines are treated as  end  of
+                    the integer value.
+        offs        offset in the buffer
+        
+        after return from this function, offs points to the char's past the value
+        being read.
+
+        This function raises an error in case unexpected symbol is found
+        ************************************************************************/
+        private static int str2int(char[] buf, ref int offs)
+        {
+            string emsg =       "ALGLIB: unable to read integer value from stream";
+            string emsg3264 =   "ALGLIB: unable to read integer value from stream (value does not fit into 32 bits)";
+            int[] sixbits = new int[12];
+            byte[] bytes = new byte[9];
+            byte[] _bytes = new byte[sizeof(int)];
+            int sixbitsread, i;
+            byte c;
+            
+            // 
+            // 1. skip leading spaces
+            // 2. read and decode six-bit digits
+            // 3. set trailing digits to zeros
+            // 4. convert to little endian 64-bit integer representation
+            // 5. check that we fit into int
+            // 6. convert to big endian representation, if needed
+            //
+            sixbitsread = 0;
+            while( buf[offs]==' ' || buf[offs]=='\t' || buf[offs]=='\n' || buf[offs]=='\r' )
+                offs++;
+            while( buf[offs]!=' ' && buf[offs]!='\t' && buf[offs]!='\n' && buf[offs]!='\r' && buf[offs]!=0 )
+            {
+                int d;
+                d = char2sixbits(buf[offs]);
+                if( d<0 || sixbitsread>=SER_ENTRY_LENGTH )
+                    throw new alglib.alglibexception(emsg);
+                sixbits[sixbitsread] = d;
+                sixbitsread++;
+                offs++;
+            }
+            if( sixbitsread==0 )
+                throw new alglib.alglibexception(emsg);
+            for(i=sixbitsread; i<12; i++)
+                sixbits[i] = 0;
+            foursixbits2threebytes(sixbits, 0, bytes, 0);
+            foursixbits2threebytes(sixbits, 4, bytes, 3);
+            foursixbits2threebytes(sixbits, 8, bytes, 6);
+            c = (bytes[sizeof(int)-1] & 0x80)!=0 ? (byte)0xFF : (byte)0x00;
+            for(i=sizeof(int); i<8; i++)
+                if( bytes[i]!=c )
+                    throw new alglib.alglibexception(emsg3264);
+            for(i=0; i<sizeof(int); i++)
+                _bytes[i] = bytes[i];        
+            if( !System.BitConverter.IsLittleEndian )
+                System.Array.Reverse(_bytes);
+            return System.BitConverter.ToInt32(_bytes,0);
+        }    
+        
+        
+        /************************************************************************
+        This function serializes double value into buffer
+
+        v           double value to be serialized
+        buf         buffer, at least 11 characters wide 
+        offs        offset in the buffer
+        
+        after return from this function, offs points to the char's past the value
+        being read.
+        ************************************************************************/
+        private static void double2str(double v, char[] buf, ref int offs)
+        {
+            int i;
+            int[] sixbits = new int[12];
+            byte[] bytes = new byte[9];
+
+            //
+            // handle special quantities
+            //
+            if( System.Double.IsNaN(v) )
+            {
+                buf[offs+0] = '.';
+                buf[offs+1] = 'n';
+                buf[offs+2] = 'a';
+                buf[offs+3] = 'n';
+                buf[offs+4] = '_';
+                buf[offs+5] = '_';
+                buf[offs+6] = '_';
+                buf[offs+7] = '_';
+                buf[offs+8] = '_';
+                buf[offs+9] = '_';
+                buf[offs+10] = '_';
+                offs += SER_ENTRY_LENGTH;
+                return;
+            }
+            if( System.Double.IsPositiveInfinity(v) )
+            {
+                buf[offs+0] = '.';
+                buf[offs+1] = 'p';
+                buf[offs+2] = 'o';
+                buf[offs+3] = 's';
+                buf[offs+4] = 'i';
+                buf[offs+5] = 'n';
+                buf[offs+6] = 'f';
+                buf[offs+7] = '_';
+                buf[offs+8] = '_';
+                buf[offs+9] = '_';
+                buf[offs+10] = '_';
+                offs += SER_ENTRY_LENGTH;
+                return;
+            }
+            if( System.Double.IsNegativeInfinity(v) )
+            {
+                buf[offs+0] = '.';
+                buf[offs+1] = 'n';
+                buf[offs+2] = 'e';
+                buf[offs+3] = 'g';
+                buf[offs+4] = 'i';
+                buf[offs+5] = 'n';
+                buf[offs+6] = 'f';
+                buf[offs+7] = '_';
+                buf[offs+8] = '_';
+                buf[offs+9] = '_';
+                buf[offs+10] = '_';
+                offs += SER_ENTRY_LENGTH;
+                return;
+            }
+            
+            //
+            // process general case:
+            // 1. copy v to array of chars
+            // 2. set 9th byte to zero in order to simplify conversion to six-bit representation
+            // 3. convert to little endian (if needed)
+            // 4. convert to six-bit representation
+            //    (last 12th element of sixbits is always zero, we do not output it)
+            //
+            byte[] _bytes = System.BitConverter.GetBytes((double)v);
+            if( !System.BitConverter.IsLittleEndian )
+                System.Array.Reverse(_bytes);
+            for(i=0; i<sizeof(double); i++)
+                bytes[i] = _bytes[i];
+            for(i=sizeof(double); i<9; i++)
+                bytes[i] = 0;
+            threebytes2foursixbits(bytes, 0, sixbits, 0);
+            threebytes2foursixbits(bytes, 3, sixbits, 4);
+            threebytes2foursixbits(bytes, 6, sixbits, 8);
+            for(i=0; i<SER_ENTRY_LENGTH; i++)
+                buf[offs+i] = sixbits2char(sixbits[i]);
+            offs += SER_ENTRY_LENGTH;
+        }
+
+        /************************************************************************
+        This function unserializes double value from string
+
+        buf         buffer which contains value; leading spaces/tabs/newlines are 
+                    ignored, traling spaces/tabs/newlines are treated as  end  of
+                    the double value.
+        offs        offset in the buffer
+        
+        after return from this function, offs points to the char's past the value
+        being read.
+
+        This function raises an error in case unexpected symbol is found
+        ************************************************************************/
+        private static double str2double(char[] buf, ref int offs)
+        {
+            string emsg = "ALGLIB: unable to read double value from stream";
+            int[] sixbits = new int[12];
+            byte[]  bytes = new byte[9];
+            byte[] _bytes = new byte[sizeof(double)];
+            int sixbitsread, i;
+            
+            
+            // 
+            // skip leading spaces
+            //
+            while( buf[offs]==' ' || buf[offs]=='\t' || buf[offs]=='\n' || buf[offs]=='\r' )
+                offs++;
+            
+              
+            //
+            // Handle special cases
+            //
+            if( buf[offs]=='.' )
+            {
+                string s = new string(buf, offs, SER_ENTRY_LENGTH);
+                if( s==".nan_______" )
+                {
+                    offs += SER_ENTRY_LENGTH;
+                    return System.Double.NaN;
+                }
+                if( s==".posinf____" )
+                {
+                    offs += SER_ENTRY_LENGTH;
+                    return System.Double.PositiveInfinity;
+                }
+                if( s==".neginf____" )
+                {
+                    offs += SER_ENTRY_LENGTH;
+                    return System.Double.NegativeInfinity;
+                }
+                throw new alglib.alglibexception(emsg);
+            }
+            
+            // 
+            // General case:
+            // 1. read and decode six-bit digits
+            // 2. check that all 11 digits were read
+            // 3. set last 12th digit to zero (needed for simplicity of conversion)
+            // 4. convert to 8 bytes
+            // 5. convert to big endian representation, if needed
+            //
+            sixbitsread = 0;
+            while( buf[offs]!=' ' && buf[offs]!='\t' && buf[offs]!='\n' && buf[offs]!='\r' && buf[offs]!=0 )
+            {
+                int d;
+                d = char2sixbits(buf[offs]);
+                if( d<0 || sixbitsread>=SER_ENTRY_LENGTH )
+                    throw new alglib.alglibexception(emsg);
+                sixbits[sixbitsread] = d;
+                sixbitsread++;
+                offs++;
+            }
+            if( sixbitsread!=SER_ENTRY_LENGTH )
+                throw new alglib.alglibexception(emsg);
+            sixbits[SER_ENTRY_LENGTH] = 0;
+            foursixbits2threebytes(sixbits, 0, bytes, 0);
+            foursixbits2threebytes(sixbits, 4, bytes, 3);
+            foursixbits2threebytes(sixbits, 8, bytes, 6);
+            for(i=0; i<sizeof(double); i++)
+                _bytes[i] = bytes[i];        
+            if( !System.BitConverter.IsLittleEndian )
+                System.Array.Reverse(_bytes);        
+            return System.BitConverter.ToDouble(_bytes,0);
+        }
+    }}
Index: /trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.5.0/ALGLIB-3.5.0/dataanalysis.cs
===================================================================
--- /trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.5.0/ALGLIB-3.5.0/dataanalysis.cs	(revision 7673)
+++ /trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.5.0/ALGLIB-3.5.0/dataanalysis.cs	(revision 7673)
@@ -0,0 +1,20192 @@
+/*************************************************************************
+Copyright (c) Sergey Bochkanov (ALGLIB project).
+
+>>> SOURCE LICENSE >>>
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation (www.fsf.org); either version 2 of the
+License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+A copy of the GNU General Public License is available at
+http://www.fsf.org/licensing/licenses
+>>> END OF LICENSE >>>
+*************************************************************************/
+#pragma warning disable 162
+#pragma warning disable 219
+using System;
+
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    Optimal binary classification
+
+    Algorithms finds optimal (=with minimal cross-entropy) binary partition.
+    Internal subroutine.
+
+    INPUT PARAMETERS:
+        A       -   array[0..N-1], variable
+        C       -   array[0..N-1], class numbers (0 or 1).
+        N       -   array size
+
+    OUTPUT PARAMETERS:
+        Info    -   completetion code:
+                    * -3, all values of A[] are same (partition is impossible)
+                    * -2, one of C[] is incorrect (<0, >1)
+                    * -1, incorrect pararemets were passed (N<=0).
+                    *  1, OK
+        Threshold-  partiton boundary. Left part contains values which are
+                    strictly less than Threshold. Right part contains values
+                    which are greater than or equal to Threshold.
+        PAL, PBL-   probabilities P(0|v<Threshold) and P(1|v<Threshold)
+        PAR, PBR-   probabilities P(0|v>=Threshold) and P(1|v>=Threshold)
+        CVE     -   cross-validation estimate of cross-entropy
+
+      -- ALGLIB --
+         Copyright 22.05.2008 by Bochkanov Sergey
+    *************************************************************************/
+    public static void dsoptimalsplit2(double[] a, int[] c, int n, out int info, out double threshold, out double pal, out double pbl, out double par, out double pbr, out double cve)
+    {
+        info = 0;
+        threshold = 0;
+        pal = 0;
+        pbl = 0;
+        par = 0;
+        pbr = 0;
+        cve = 0;
+        bdss.dsoptimalsplit2(a, c, n, ref info, ref threshold, ref pal, ref pbl, ref par, ref pbr, ref cve);
+        return;
+    }
+
+    /*************************************************************************
+    Optimal partition, internal subroutine. Fast version.
+
+    Accepts:
+        A       array[0..N-1]       array of attributes     array[0..N-1]
+        C       array[0..N-1]       array of class labels
+        TiesBuf array[0..N]         temporaries (ties)
+        CntBuf  array[0..2*NC-1]    temporaries (counts)
+        Alpha                       centering factor (0<=alpha<=1, recommended value - 0.05)
+        BufR    array[0..N-1]       temporaries
+        BufI    array[0..N-1]       temporaries
+
+    Output:
+        Info    error code (">0"=OK, "<0"=bad)
+        RMS     training set RMS error
+        CVRMS   leave-one-out RMS error
+
+    Note:
+        content of all arrays is changed by subroutine;
+        it doesn't allocate temporaries.
+
+      -- ALGLIB --
+         Copyright 11.12.2008 by Bochkanov Sergey
+    *************************************************************************/
+    public static void dsoptimalsplit2fast(ref double[] a, ref int[] c, ref int[] tiesbuf, ref int[] cntbuf, ref double[] bufr, ref int[] bufi, int n, int nc, double alpha, out int info, out double threshold, out double rms, out double cvrms)
+    {
+        info = 0;
+        threshold = 0;
+        rms = 0;
+        cvrms = 0;
+        bdss.dsoptimalsplit2fast(ref a, ref c, ref tiesbuf, ref cntbuf, ref bufr, ref bufi, n, nc, alpha, ref info, ref threshold, ref rms, ref cvrms);
+        return;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+
+    *************************************************************************/
+    public class decisionforest
+    {
+        //
+        // Public declarations
+        //
+
+        public decisionforest()
+        {
+            _innerobj = new dforest.decisionforest();
+        }
+
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private dforest.decisionforest _innerobj;
+        public dforest.decisionforest innerobj { get { return _innerobj; } }
+        public decisionforest(dforest.decisionforest obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+
+    /*************************************************************************
+
+    *************************************************************************/
+    public class dfreport
+    {
+        //
+        // Public declarations
+        //
+        public double relclserror { get { return _innerobj.relclserror; } set { _innerobj.relclserror = value; } }
+        public double avgce { get { return _innerobj.avgce; } set { _innerobj.avgce = value; } }
+        public double rmserror { get { return _innerobj.rmserror; } set { _innerobj.rmserror = value; } }
+        public double avgerror { get { return _innerobj.avgerror; } set { _innerobj.avgerror = value; } }
+        public double avgrelerror { get { return _innerobj.avgrelerror; } set { _innerobj.avgrelerror = value; } }
+        public double oobrelclserror { get { return _innerobj.oobrelclserror; } set { _innerobj.oobrelclserror = value; } }
+        public double oobavgce { get { return _innerobj.oobavgce; } set { _innerobj.oobavgce = value; } }
+        public double oobrmserror { get { return _innerobj.oobrmserror; } set { _innerobj.oobrmserror = value; } }
+        public double oobavgerror { get { return _innerobj.oobavgerror; } set { _innerobj.oobavgerror = value; } }
+        public double oobavgrelerror { get { return _innerobj.oobavgrelerror; } set { _innerobj.oobavgrelerror = value; } }
+
+        public dfreport()
+        {
+            _innerobj = new dforest.dfreport();
+        }
+
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private dforest.dfreport _innerobj;
+        public dforest.dfreport innerobj { get { return _innerobj; } }
+        public dfreport(dforest.dfreport obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+
+    /*************************************************************************
+    This function serializes data structure to string.
+
+    Important properties of s_out:
+    * it contains alphanumeric characters, dots, underscores, minus signs
+    * these symbols are grouped into words, which are separated by spaces
+      and Windows-style (CR+LF) newlines
+    * although  serializer  uses  spaces and CR+LF as separators, you can 
+      replace any separator character by arbitrary combination of spaces,
+      tabs, Windows or Unix newlines. It allows flexible reformatting  of
+      the  string  in  case you want to include it into text or XML file. 
+      But you should not insert separators into the middle of the "words"
+      nor you should change case of letters.
+    * s_out can be freely moved between 32-bit and 64-bit systems, little
+      and big endian machines, and so on. You can serialize structure  on
+      32-bit machine and unserialize it on 64-bit one (or vice versa), or
+      serialize  it  on  SPARC  and  unserialize  on  x86.  You  can also 
+      serialize  it  in  C# version of ALGLIB and unserialize in C++ one, 
+      and vice versa.
+    *************************************************************************/
+    public static void dfserialize(decisionforest obj, out string s_out)
+    {
+        alglib.serializer s = new alglib.serializer();
+        s.alloc_start();
+        dforest.dfalloc(s, obj.innerobj);
+        s.sstart_str();
+        dforest.dfserialize(s, obj.innerobj);
+        s.stop();
+        s_out = s.get_string();
+    }
+
+
+    /*************************************************************************
+    This function unserializes data structure from string.
+    *************************************************************************/
+    public static void dfunserialize(string s_in, out decisionforest obj)
+    {
+        alglib.serializer s = new alglib.serializer();
+        obj = new decisionforest();
+        s.ustart_str(s_in);
+        dforest.dfunserialize(s, obj.innerobj);
+        s.stop();
+    }
+
+    /*************************************************************************
+    This subroutine builds random decision forest.
+
+    INPUT PARAMETERS:
+        XY          -   training set
+        NPoints     -   training set size, NPoints>=1
+        NVars       -   number of independent variables, NVars>=1
+        NClasses    -   task type:
+                        * NClasses=1 - regression task with one
+                                       dependent variable
+                        * NClasses>1 - classification task with
+                                       NClasses classes.
+        NTrees      -   number of trees in a forest, NTrees>=1.
+                        recommended values: 50-100.
+        R           -   percent of a training set used to build
+                        individual trees. 0<R<=1.
+                        recommended values: 0.1 <= R <= 0.66.
+
+    OUTPUT PARAMETERS:
+        Info        -   return code:
+                        * -2, if there is a point with class number
+                              outside of [0..NClasses-1].
+                        * -1, if incorrect parameters was passed
+                              (NPoints<1, NVars<1, NClasses<1, NTrees<1, R<=0
+                              or R>1).
+                        *  1, if task has been solved
+        DF          -   model built
+        Rep         -   training report, contains error on a training set
+                        and out-of-bag estimates of generalization error.
+
+      -- ALGLIB --
+         Copyright 19.02.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void dfbuildrandomdecisionforest(double[,] xy, int npoints, int nvars, int nclasses, int ntrees, double r, out int info, out decisionforest df, out dfreport rep)
+    {
+        info = 0;
+        df = new decisionforest();
+        rep = new dfreport();
+        dforest.dfbuildrandomdecisionforest(xy, npoints, nvars, nclasses, ntrees, r, ref info, df.innerobj, rep.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    This subroutine builds random decision forest.
+    This function gives ability to tune number of variables used when choosing
+    best split.
+
+    INPUT PARAMETERS:
+        XY          -   training set
+        NPoints     -   training set size, NPoints>=1
+        NVars       -   number of independent variables, NVars>=1
+        NClasses    -   task type:
+                        * NClasses=1 - regression task with one
+                                       dependent variable
+                        * NClasses>1 - classification task with
+                                       NClasses classes.
+        NTrees      -   number of trees in a forest, NTrees>=1.
+                        recommended values: 50-100.
+        NRndVars    -   number of variables used when choosing best split
+        R           -   percent of a training set used to build
+                        individual trees. 0<R<=1.
+                        recommended values: 0.1 <= R <= 0.66.
+
+    OUTPUT PARAMETERS:
+        Info        -   return code:
+                        * -2, if there is a point with class number
+                              outside of [0..NClasses-1].
+                        * -1, if incorrect parameters was passed
+                              (NPoints<1, NVars<1, NClasses<1, NTrees<1, R<=0
+                              or R>1).
+                        *  1, if task has been solved
+        DF          -   model built
+        Rep         -   training report, contains error on a training set
+                        and out-of-bag estimates of generalization error.
+
+      -- ALGLIB --
+         Copyright 19.02.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void dfbuildrandomdecisionforestx1(double[,] xy, int npoints, int nvars, int nclasses, int ntrees, int nrndvars, double r, out int info, out decisionforest df, out dfreport rep)
+    {
+        info = 0;
+        df = new decisionforest();
+        rep = new dfreport();
+        dforest.dfbuildrandomdecisionforestx1(xy, npoints, nvars, nclasses, ntrees, nrndvars, r, ref info, df.innerobj, rep.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    Procesing
+
+    INPUT PARAMETERS:
+        DF      -   decision forest model
+        X       -   input vector,  array[0..NVars-1].
+
+    OUTPUT PARAMETERS:
+        Y       -   result. Regression estimate when solving regression  task,
+                    vector of posterior probabilities for classification task.
+
+    See also DFProcessI.
+
+      -- ALGLIB --
+         Copyright 16.02.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void dfprocess(decisionforest df, double[] x, ref double[] y)
+    {
+
+        dforest.dfprocess(df.innerobj, x, ref y);
+        return;
+    }
+
+    /*************************************************************************
+    'interactive' variant of DFProcess for languages like Python which support
+    constructs like "Y = DFProcessI(DF,X)" and interactive mode of interpreter
+
+    This function allocates new array on each call,  so  it  is  significantly
+    slower than its 'non-interactive' counterpart, but it is  more  convenient
+    when you call it from command line.
+
+      -- ALGLIB --
+         Copyright 28.02.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void dfprocessi(decisionforest df, double[] x, out double[] y)
+    {
+        y = new double[0];
+        dforest.dfprocessi(df.innerobj, x, ref y);
+        return;
+    }
+
+    /*************************************************************************
+    Relative classification error on the test set
+
+    INPUT PARAMETERS:
+        DF      -   decision forest model
+        XY      -   test set
+        NPoints -   test set size
+
+    RESULT:
+        percent of incorrectly classified cases.
+        Zero if model solves regression task.
+
+      -- ALGLIB --
+         Copyright 16.02.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static double dfrelclserror(decisionforest df, double[,] xy, int npoints)
+    {
+
+        double result = dforest.dfrelclserror(df.innerobj, xy, npoints);
+        return result;
+    }
+
+    /*************************************************************************
+    Average cross-entropy (in bits per element) on the test set
+
+    INPUT PARAMETERS:
+        DF      -   decision forest model
+        XY      -   test set
+        NPoints -   test set size
+
+    RESULT:
+        CrossEntropy/(NPoints*LN(2)).
+        Zero if model solves regression task.
+
+      -- ALGLIB --
+         Copyright 16.02.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static double dfavgce(decisionforest df, double[,] xy, int npoints)
+    {
+
+        double result = dforest.dfavgce(df.innerobj, xy, npoints);
+        return result;
+    }
+
+    /*************************************************************************
+    RMS error on the test set
+
+    INPUT PARAMETERS:
+        DF      -   decision forest model
+        XY      -   test set
+        NPoints -   test set size
+
+    RESULT:
+        root mean square error.
+        Its meaning for regression task is obvious. As for
+        classification task, RMS error means error when estimating posterior
+        probabilities.
+
+      -- ALGLIB --
+         Copyright 16.02.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static double dfrmserror(decisionforest df, double[,] xy, int npoints)
+    {
+
+        double result = dforest.dfrmserror(df.innerobj, xy, npoints);
+        return result;
+    }
+
+    /*************************************************************************
+    Average error on the test set
+
+    INPUT PARAMETERS:
+        DF      -   decision forest model
+        XY      -   test set
+        NPoints -   test set size
+
+    RESULT:
+        Its meaning for regression task is obvious. As for
+        classification task, it means average error when estimating posterior
+        probabilities.
+
+      -- ALGLIB --
+         Copyright 16.02.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static double dfavgerror(decisionforest df, double[,] xy, int npoints)
+    {
+
+        double result = dforest.dfavgerror(df.innerobj, xy, npoints);
+        return result;
+    }
+
+    /*************************************************************************
+    Average relative error on the test set
+
+    INPUT PARAMETERS:
+        DF      -   decision forest model
+        XY      -   test set
+        NPoints -   test set size
+
+    RESULT:
+        Its meaning for regression task is obvious. As for
+        classification task, it means average relative error when estimating
+        posterior probability of belonging to the correct class.
+
+      -- ALGLIB --
+         Copyright 16.02.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static double dfavgrelerror(decisionforest df, double[,] xy, int npoints)
+    {
+
+        double result = dforest.dfavgrelerror(df.innerobj, xy, npoints);
+        return result;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+
+    *************************************************************************/
+    public class linearmodel
+    {
+        //
+        // Public declarations
+        //
+
+        public linearmodel()
+        {
+            _innerobj = new linreg.linearmodel();
+        }
+
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private linreg.linearmodel _innerobj;
+        public linreg.linearmodel innerobj { get { return _innerobj; } }
+        public linearmodel(linreg.linearmodel obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+
+    /*************************************************************************
+    LRReport structure contains additional information about linear model:
+    * C             -   covariation matrix,  array[0..NVars,0..NVars].
+                        C[i,j] = Cov(A[i],A[j])
+    * RMSError      -   root mean square error on a training set
+    * AvgError      -   average error on a training set
+    * AvgRelError   -   average relative error on a training set (excluding
+                        observations with zero function value).
+    * CVRMSError    -   leave-one-out cross-validation estimate of
+                        generalization error. Calculated using fast algorithm
+                        with O(NVars*NPoints) complexity.
+    * CVAvgError    -   cross-validation estimate of average error
+    * CVAvgRelError -   cross-validation estimate of average relative error
+
+    All other fields of the structure are intended for internal use and should
+    not be used outside ALGLIB.
+    *************************************************************************/
+    public class lrreport
+    {
+        //
+        // Public declarations
+        //
+        public double[,] c { get { return _innerobj.c; } set { _innerobj.c = value; } }
+        public double rmserror { get { return _innerobj.rmserror; } set { _innerobj.rmserror = value; } }
+        public double avgerror { get { return _innerobj.avgerror; } set { _innerobj.avgerror = value; } }
+        public double avgrelerror { get { return _innerobj.avgrelerror; } set { _innerobj.avgrelerror = value; } }
+        public double cvrmserror { get { return _innerobj.cvrmserror; } set { _innerobj.cvrmserror = value; } }
+        public double cvavgerror { get { return _innerobj.cvavgerror; } set { _innerobj.cvavgerror = value; } }
+        public double cvavgrelerror { get { return _innerobj.cvavgrelerror; } set { _innerobj.cvavgrelerror = value; } }
+        public int ncvdefects { get { return _innerobj.ncvdefects; } set { _innerobj.ncvdefects = value; } }
+        public int[] cvdefects { get { return _innerobj.cvdefects; } set { _innerobj.cvdefects = value; } }
+
+        public lrreport()
+        {
+            _innerobj = new linreg.lrreport();
+        }
+
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private linreg.lrreport _innerobj;
+        public linreg.lrreport innerobj { get { return _innerobj; } }
+        public lrreport(linreg.lrreport obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+    /*************************************************************************
+    Linear regression
+
+    Subroutine builds model:
+
+        Y = A(0)*X[0] + ... + A(N-1)*X[N-1] + A(N)
+
+    and model found in ALGLIB format, covariation matrix, training set  errors
+    (rms,  average,  average  relative)   and  leave-one-out  cross-validation
+    estimate of the generalization error. CV  estimate calculated  using  fast
+    algorithm with O(NPoints*NVars) complexity.
+
+    When  covariation  matrix  is  calculated  standard deviations of function
+    values are assumed to be equal to RMS error on the training set.
+
+    INPUT PARAMETERS:
+        XY          -   training set, array [0..NPoints-1,0..NVars]:
+                        * NVars columns - independent variables
+                        * last column - dependent variable
+        NPoints     -   training set size, NPoints>NVars+1
+        NVars       -   number of independent variables
+
+    OUTPUT PARAMETERS:
+        Info        -   return code:
+                        * -255, in case of unknown internal error
+                        * -4, if internal SVD subroutine haven't converged
+                        * -1, if incorrect parameters was passed (NPoints<NVars+2, NVars<1).
+                        *  1, if subroutine successfully finished
+        LM          -   linear model in the ALGLIB format. Use subroutines of
+                        this unit to work with the model.
+        AR          -   additional results
+
+
+      -- ALGLIB --
+         Copyright 02.08.2008 by Bochkanov Sergey
+    *************************************************************************/
+    public static void lrbuild(double[,] xy, int npoints, int nvars, out int info, out linearmodel lm, out lrreport ar)
+    {
+        info = 0;
+        lm = new linearmodel();
+        ar = new lrreport();
+        linreg.lrbuild(xy, npoints, nvars, ref info, lm.innerobj, ar.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    Linear regression
+
+    Variant of LRBuild which uses vector of standatd deviations (errors in
+    function values).
+
+    INPUT PARAMETERS:
+        XY          -   training set, array [0..NPoints-1,0..NVars]:
+                        * NVars columns - independent variables
+                        * last column - dependent variable
+        S           -   standard deviations (errors in function values)
+                        array[0..NPoints-1], S[i]>0.
+        NPoints     -   training set size, NPoints>NVars+1
+        NVars       -   number of independent variables
+
+    OUTPUT PARAMETERS:
+        Info        -   return code:
+                        * -255, in case of unknown internal error
+                        * -4, if internal SVD subroutine haven't converged
+                        * -1, if incorrect parameters was passed (NPoints<NVars+2, NVars<1).
+                        * -2, if S[I]<=0
+                        *  1, if subroutine successfully finished
+        LM          -   linear model in the ALGLIB format. Use subroutines of
+                        this unit to work with the model.
+        AR          -   additional results
+
+
+      -- ALGLIB --
+         Copyright 02.08.2008 by Bochkanov Sergey
+    *************************************************************************/
+    public static void lrbuilds(double[,] xy, double[] s, int npoints, int nvars, out int info, out linearmodel lm, out lrreport ar)
+    {
+        info = 0;
+        lm = new linearmodel();
+        ar = new lrreport();
+        linreg.lrbuilds(xy, s, npoints, nvars, ref info, lm.innerobj, ar.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    Like LRBuildS, but builds model
+
+        Y = A(0)*X[0] + ... + A(N-1)*X[N-1]
+
+    i.e. with zero constant term.
+
+      -- ALGLIB --
+         Copyright 30.10.2008 by Bochkanov Sergey
+    *************************************************************************/
+    public static void lrbuildzs(double[,] xy, double[] s, int npoints, int nvars, out int info, out linearmodel lm, out lrreport ar)
+    {
+        info = 0;
+        lm = new linearmodel();
+        ar = new lrreport();
+        linreg.lrbuildzs(xy, s, npoints, nvars, ref info, lm.innerobj, ar.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    Like LRBuild but builds model
+
+        Y = A(0)*X[0] + ... + A(N-1)*X[N-1]
+
+    i.e. with zero constant term.
+
+      -- ALGLIB --
+         Copyright 30.10.2008 by Bochkanov Sergey
+    *************************************************************************/
+    public static void lrbuildz(double[,] xy, int npoints, int nvars, out int info, out linearmodel lm, out lrreport ar)
+    {
+        info = 0;
+        lm = new linearmodel();
+        ar = new lrreport();
+        linreg.lrbuildz(xy, npoints, nvars, ref info, lm.innerobj, ar.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    Unpacks coefficients of linear model.
+
+    INPUT PARAMETERS:
+        LM          -   linear model in ALGLIB format
+
+    OUTPUT PARAMETERS:
+        V           -   coefficients, array[0..NVars]
+                        constant term (intercept) is stored in the V[NVars].
+        NVars       -   number of independent variables (one less than number
+                        of coefficients)
+
+      -- ALGLIB --
+         Copyright 30.08.2008 by Bochkanov Sergey
+    *************************************************************************/
+    public static void lrunpack(linearmodel lm, out double[] v, out int nvars)
+    {
+        v = new double[0];
+        nvars = 0;
+        linreg.lrunpack(lm.innerobj, ref v, ref nvars);
+        return;
+    }
+
+    /*************************************************************************
+    "Packs" coefficients and creates linear model in ALGLIB format (LRUnpack
+    reversed).
+
+    INPUT PARAMETERS:
+        V           -   coefficients, array[0..NVars]
+        NVars       -   number of independent variables
+
+    OUTPUT PAREMETERS:
+        LM          -   linear model.
+
+      -- ALGLIB --
+         Copyright 30.08.2008 by Bochkanov Sergey
+    *************************************************************************/
+    public static void lrpack(double[] v, int nvars, out linearmodel lm)
+    {
+        lm = new linearmodel();
+        linreg.lrpack(v, nvars, lm.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    Procesing
+
+    INPUT PARAMETERS:
+        LM      -   linear model
+        X       -   input vector,  array[0..NVars-1].
+
+    Result:
+        value of linear model regression estimate
+
+      -- ALGLIB --
+         Copyright 03.09.2008 by Bochkanov Sergey
+    *************************************************************************/
+    public static double lrprocess(linearmodel lm, double[] x)
+    {
+
+        double result = linreg.lrprocess(lm.innerobj, x);
+        return result;
+    }
+
+    /*************************************************************************
+    RMS error on the test set
+
+    INPUT PARAMETERS:
+        LM      -   linear model
+        XY      -   test set
+        NPoints -   test set size
+
+    RESULT:
+        root mean square error.
+
+      -- ALGLIB --
+         Copyright 30.08.2008 by Bochkanov Sergey
+    *************************************************************************/
+    public static double lrrmserror(linearmodel lm, double[,] xy, int npoints)
+    {
+
+        double result = linreg.lrrmserror(lm.innerobj, xy, npoints);
+        return result;
+    }
+
+    /*************************************************************************
+    Average error on the test set
+
+    INPUT PARAMETERS:
+        LM      -   linear model
+        XY      -   test set
+        NPoints -   test set size
+
+    RESULT:
+        average error.
+
+      -- ALGLIB --
+         Copyright 30.08.2008 by Bochkanov Sergey
+    *************************************************************************/
+    public static double lravgerror(linearmodel lm, double[,] xy, int npoints)
+    {
+
+        double result = linreg.lravgerror(lm.innerobj, xy, npoints);
+        return result;
+    }
+
+    /*************************************************************************
+    RMS error on the test set
+
+    INPUT PARAMETERS:
+        LM      -   linear model
+        XY      -   test set
+        NPoints -   test set size
+
+    RESULT:
+        average relative error.
+
+      -- ALGLIB --
+         Copyright 30.08.2008 by Bochkanov Sergey
+    *************************************************************************/
+    public static double lravgrelerror(linearmodel lm, double[,] xy, int npoints)
+    {
+
+        double result = linreg.lravgrelerror(lm.innerobj, xy, npoints);
+        return result;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    Filters: simple moving averages (unsymmetric).
+
+    This filter replaces array by results of SMA(K) filter. SMA(K) is defined
+    as filter which averages at most K previous points (previous - not points
+    AROUND central point) - or less, in case of the first K-1 points.
+
+    INPUT PARAMETERS:
+        X           -   array[N], array to process. It can be larger than N,
+                        in this case only first N points are processed.
+        N           -   points count, N>=0
+        K           -   K>=1 (K can be larger than N ,  such  cases  will  be
+                        correctly handled). Window width. K=1 corresponds  to
+                        identity transformation (nothing changes).
+
+    OUTPUT PARAMETERS:
+        X           -   array, whose first N elements were processed with SMA(K)
+
+    NOTE 1: this function uses efficient in-place  algorithm  which  does not
+            allocate temporary arrays.
+
+    NOTE 2: this algorithm makes only one pass through array and uses running
+            sum  to speed-up calculation of the averages. Additional measures
+            are taken to ensure that running sum on a long sequence  of  zero
+            elements will be correctly reset to zero even in the presence  of
+            round-off error.
+
+    NOTE 3: this  is  unsymmetric version of the algorithm,  which  does  NOT
+            averages points after the current one. Only X[i], X[i-1], ... are
+            used when calculating new value of X[i]. We should also note that
+            this algorithm uses BOTH previous points and  current  one,  i.e.
+            new value of X[i] depends on BOTH previous point and X[i] itself.
+
+      -- ALGLIB --
+         Copyright 25.10.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void filtersma(ref double[] x, int n, int k)
+    {
+
+        filters.filtersma(ref x, n, k);
+        return;
+    }
+    public static void filtersma(ref double[] x, int k)
+    {
+        int n;
+
+
+        n = ap.len(x);
+        filters.filtersma(ref x, n, k);
+
+        return;
+    }
+
+    /*************************************************************************
+    Filters: exponential moving averages.
+
+    This filter replaces array by results of EMA(alpha) filter. EMA(alpha) is
+    defined as filter which replaces X[] by S[]:
+        S[0] = X[0]
+        S[t] = alpha*X[t] + (1-alpha)*S[t-1]
+
+    INPUT PARAMETERS:
+        X           -   array[N], array to process. It can be larger than N,
+                        in this case only first N points are processed.
+        N           -   points count, N>=0
+        alpha       -   0<alpha<=1, smoothing parameter.
+
+    OUTPUT PARAMETERS:
+        X           -   array, whose first N elements were processed
+                        with EMA(alpha)
+
+    NOTE 1: this function uses efficient in-place  algorithm  which  does not
+            allocate temporary arrays.
+
+    NOTE 2: this algorithm uses BOTH previous points and  current  one,  i.e.
+            new value of X[i] depends on BOTH previous point and X[i] itself.
+
+    NOTE 3: technical analytis users quite often work  with  EMA  coefficient
+            expressed in DAYS instead of fractions. If you want to  calculate
+            EMA(N), where N is a number of days, you can use alpha=2/(N+1).
+
+      -- ALGLIB --
+         Copyright 25.10.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void filterema(ref double[] x, int n, double alpha)
+    {
+
+        filters.filterema(ref x, n, alpha);
+        return;
+    }
+    public static void filterema(ref double[] x, double alpha)
+    {
+        int n;
+
+
+        n = ap.len(x);
+        filters.filterema(ref x, n, alpha);
+
+        return;
+    }
+
+    /*************************************************************************
+    Filters: linear regression moving averages.
+
+    This filter replaces array by results of LRMA(K) filter.
+
+    LRMA(K) is defined as filter which, for each data  point,  builds  linear
+    regression  model  using  K  prevous  points (point itself is included in
+    these K points) and calculates value of this linear model at the point in
+    question.
+
+    INPUT PARAMETERS:
+        X           -   array[N], array to process. It can be larger than N,
+                        in this case only first N points are processed.
+        N           -   points count, N>=0
+        K           -   K>=1 (K can be larger than N ,  such  cases  will  be
+                        correctly handled). Window width. K=1 corresponds  to
+                        identity transformation (nothing changes).
+
+    OUTPUT PARAMETERS:
+        X           -   array, whose first N elements were processed with SMA(K)
+
+    NOTE 1: this function uses efficient in-place  algorithm  which  does not
+            allocate temporary arrays.
+
+    NOTE 2: this algorithm makes only one pass through array and uses running
+            sum  to speed-up calculation of the averages. Additional measures
+            are taken to ensure that running sum on a long sequence  of  zero
+            elements will be correctly reset to zero even in the presence  of
+            round-off error.
+
+    NOTE 3: this  is  unsymmetric version of the algorithm,  which  does  NOT
+            averages points after the current one. Only X[i], X[i-1], ... are
+            used when calculating new value of X[i]. We should also note that
+            this algorithm uses BOTH previous points and  current  one,  i.e.
+            new value of X[i] depends on BOTH previous point and X[i] itself.
+
+      -- ALGLIB --
+         Copyright 25.10.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void filterlrma(ref double[] x, int n, int k)
+    {
+
+        filters.filterlrma(ref x, n, k);
+        return;
+    }
+    public static void filterlrma(ref double[] x, int k)
+    {
+        int n;
+
+
+        n = ap.len(x);
+        filters.filterlrma(ref x, n, k);
+
+        return;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    k-means++ clusterization
+
+    INPUT PARAMETERS:
+        XY          -   dataset, array [0..NPoints-1,0..NVars-1].
+        NPoints     -   dataset size, NPoints>=K
+        NVars       -   number of variables, NVars>=1
+        K           -   desired number of clusters, K>=1
+        Restarts    -   number of restarts, Restarts>=1
+
+    OUTPUT PARAMETERS:
+        Info        -   return code:
+                        * -3, if task is degenerate (number of distinct points is
+                              less than K)
+                        * -1, if incorrect NPoints/NFeatures/K/Restarts was passed
+                        *  1, if subroutine finished successfully
+        C           -   array[0..NVars-1,0..K-1].matrix whose columns store
+                        cluster's centers
+        XYC         -   array[NPoints], which contains cluster indexes
+
+      -- ALGLIB --
+         Copyright 21.03.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void kmeansgenerate(double[,] xy, int npoints, int nvars, int k, int restarts, out int info, out double[,] c, out int[] xyc)
+    {
+        info = 0;
+        c = new double[0,0];
+        xyc = new int[0];
+        kmeans.kmeansgenerate(xy, npoints, nvars, k, restarts, ref info, ref c, ref xyc);
+        return;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    Multiclass Fisher LDA
+
+    Subroutine finds coefficients of linear combination which optimally separates
+    training set on classes.
+
+    INPUT PARAMETERS:
+        XY          -   training set, array[0..NPoints-1,0..NVars].
+                        First NVars columns store values of independent
+                        variables, next column stores number of class (from 0
+                        to NClasses-1) which dataset element belongs to. Fractional
+                        values are rounded to nearest integer.
+        NPoints     -   training set size, NPoints>=0
+        NVars       -   number of independent variables, NVars>=1
+        NClasses    -   number of classes, NClasses>=2
+
+
+    OUTPUT PARAMETERS:
+        Info        -   return code:
+                        * -4, if internal EVD subroutine hasn't converged
+                        * -2, if there is a point with class number
+                              outside of [0..NClasses-1].
+                        * -1, if incorrect parameters was passed (NPoints<0,
+                              NVars<1, NClasses<2)
+                        *  1, if task has been solved
+                        *  2, if there was a multicollinearity in training set,
+                              but task has been solved.
+        W           -   linear combination coefficients, array[0..NVars-1]
+
+      -- ALGLIB --
+         Copyright 31.05.2008 by Bochkanov Sergey
+    *************************************************************************/
+    public static void fisherlda(double[,] xy, int npoints, int nvars, int nclasses, out int info, out double[] w)
+    {
+        info = 0;
+        w = new double[0];
+        lda.fisherlda(xy, npoints, nvars, nclasses, ref info, ref w);
+        return;
+    }
+
+    /*************************************************************************
+    N-dimensional multiclass Fisher LDA
+
+    Subroutine finds coefficients of linear combinations which optimally separates
+    training set on classes. It returns N-dimensional basis whose vector are sorted
+    by quality of training set separation (in descending order).
+
+    INPUT PARAMETERS:
+        XY          -   training set, array[0..NPoints-1,0..NVars].
+                        First NVars columns store values of independent
+                        variables, next column stores number of class (from 0
+                        to NClasses-1) which dataset element belongs to. Fractional
+                        values are rounded to nearest integer.
+        NPoints     -   training set size, NPoints>=0
+        NVars       -   number of independent variables, NVars>=1
+        NClasses    -   number of classes, NClasses>=2
+
+
+    OUTPUT PARAMETERS:
+        Info        -   return code:
+                        * -4, if internal EVD subroutine hasn't converged
+                        * -2, if there is a point with class number
+                              outside of [0..NClasses-1].
+                        * -1, if incorrect parameters was passed (NPoints<0,
+                              NVars<1, NClasses<2)
+                        *  1, if task has been solved
+                        *  2, if there was a multicollinearity in training set,
+                              but task has been solved.
+        W           -   basis, array[0..NVars-1,0..NVars-1]
+                        columns of matrix stores basis vectors, sorted by
+                        quality of training set separation (in descending order)
+
+      -- ALGLIB --
+         Copyright 31.05.2008 by Bochkanov Sergey
+    *************************************************************************/
+    public static void fisherldan(double[,] xy, int npoints, int nvars, int nclasses, out int info, out double[,] w)
+    {
+        info = 0;
+        w = new double[0,0];
+        lda.fisherldan(xy, npoints, nvars, nclasses, ref info, ref w);
+        return;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+
+    *************************************************************************/
+    public class multilayerperceptron
+    {
+        //
+        // Public declarations
+        //
+
+        public multilayerperceptron()
+        {
+            _innerobj = new mlpbase.multilayerperceptron();
+        }
+
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private mlpbase.multilayerperceptron _innerobj;
+        public mlpbase.multilayerperceptron innerobj { get { return _innerobj; } }
+        public multilayerperceptron(mlpbase.multilayerperceptron obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+
+    /*************************************************************************
+    This function serializes data structure to string.
+
+    Important properties of s_out:
+    * it contains alphanumeric characters, dots, underscores, minus signs
+    * these symbols are grouped into words, which are separated by spaces
+      and Windows-style (CR+LF) newlines
+    * although  serializer  uses  spaces and CR+LF as separators, you can 
+      replace any separator character by arbitrary combination of spaces,
+      tabs, Windows or Unix newlines. It allows flexible reformatting  of
+      the  string  in  case you want to include it into text or XML file. 
+      But you should not insert separators into the middle of the "words"
+      nor you should change case of letters.
+    * s_out can be freely moved between 32-bit and 64-bit systems, little
+      and big endian machines, and so on. You can serialize structure  on
+      32-bit machine and unserialize it on 64-bit one (or vice versa), or
+      serialize  it  on  SPARC  and  unserialize  on  x86.  You  can also 
+      serialize  it  in  C# version of ALGLIB and unserialize in C++ one, 
+      and vice versa.
+    *************************************************************************/
+    public static void mlpserialize(multilayerperceptron obj, out string s_out)
+    {
+        alglib.serializer s = new alglib.serializer();
+        s.alloc_start();
+        mlpbase.mlpalloc(s, obj.innerobj);
+        s.sstart_str();
+        mlpbase.mlpserialize(s, obj.innerobj);
+        s.stop();
+        s_out = s.get_string();
+    }
+
+
+    /*************************************************************************
+    This function unserializes data structure from string.
+    *************************************************************************/
+    public static void mlpunserialize(string s_in, out multilayerperceptron obj)
+    {
+        alglib.serializer s = new alglib.serializer();
+        obj = new multilayerperceptron();
+        s.ustart_str(s_in);
+        mlpbase.mlpunserialize(s, obj.innerobj);
+        s.stop();
+    }
+
+    /*************************************************************************
+    Creates  neural  network  with  NIn  inputs,  NOut outputs, without hidden
+    layers, with linear output layer. Network weights are  filled  with  small
+    random values.
+
+      -- ALGLIB --
+         Copyright 04.11.2007 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlpcreate0(int nin, int nout, out multilayerperceptron network)
+    {
+        network = new multilayerperceptron();
+        mlpbase.mlpcreate0(nin, nout, network.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    Same  as  MLPCreate0,  but  with  one  hidden  layer  (NHid  neurons) with
+    non-linear activation function. Output layer is linear.
+
+      -- ALGLIB --
+         Copyright 04.11.2007 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlpcreate1(int nin, int nhid, int nout, out multilayerperceptron network)
+    {
+        network = new multilayerperceptron();
+        mlpbase.mlpcreate1(nin, nhid, nout, network.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    Same as MLPCreate0, but with two hidden layers (NHid1 and  NHid2  neurons)
+    with non-linear activation function. Output layer is linear.
+     $ALL
+
+      -- ALGLIB --
+         Copyright 04.11.2007 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlpcreate2(int nin, int nhid1, int nhid2, int nout, out multilayerperceptron network)
+    {
+        network = new multilayerperceptron();
+        mlpbase.mlpcreate2(nin, nhid1, nhid2, nout, network.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    Creates  neural  network  with  NIn  inputs,  NOut outputs, without hidden
+    layers with non-linear output layer. Network weights are filled with small
+    random values.
+
+    Activation function of the output layer takes values:
+
+        (B, +INF), if D>=0
+
+    or
+
+        (-INF, B), if D<0.
+
+
+      -- ALGLIB --
+         Copyright 30.03.2008 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlpcreateb0(int nin, int nout, double b, double d, out multilayerperceptron network)
+    {
+        network = new multilayerperceptron();
+        mlpbase.mlpcreateb0(nin, nout, b, d, network.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    Same as MLPCreateB0 but with non-linear hidden layer.
+
+      -- ALGLIB --
+         Copyright 30.03.2008 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlpcreateb1(int nin, int nhid, int nout, double b, double d, out multilayerperceptron network)
+    {
+        network = new multilayerperceptron();
+        mlpbase.mlpcreateb1(nin, nhid, nout, b, d, network.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    Same as MLPCreateB0 but with two non-linear hidden layers.
+
+      -- ALGLIB --
+         Copyright 30.03.2008 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlpcreateb2(int nin, int nhid1, int nhid2, int nout, double b, double d, out multilayerperceptron network)
+    {
+        network = new multilayerperceptron();
+        mlpbase.mlpcreateb2(nin, nhid1, nhid2, nout, b, d, network.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    Creates  neural  network  with  NIn  inputs,  NOut outputs, without hidden
+    layers with non-linear output layer. Network weights are filled with small
+    random values. Activation function of the output layer takes values [A,B].
+
+      -- ALGLIB --
+         Copyright 30.03.2008 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlpcreater0(int nin, int nout, double a, double b, out multilayerperceptron network)
+    {
+        network = new multilayerperceptron();
+        mlpbase.mlpcreater0(nin, nout, a, b, network.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    Same as MLPCreateR0, but with non-linear hidden layer.
+
+      -- ALGLIB --
+         Copyright 30.03.2008 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlpcreater1(int nin, int nhid, int nout, double a, double b, out multilayerperceptron network)
+    {
+        network = new multilayerperceptron();
+        mlpbase.mlpcreater1(nin, nhid, nout, a, b, network.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    Same as MLPCreateR0, but with two non-linear hidden layers.
+
+      -- ALGLIB --
+         Copyright 30.03.2008 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlpcreater2(int nin, int nhid1, int nhid2, int nout, double a, double b, out multilayerperceptron network)
+    {
+        network = new multilayerperceptron();
+        mlpbase.mlpcreater2(nin, nhid1, nhid2, nout, a, b, network.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    Creates classifier network with NIn  inputs  and  NOut  possible  classes.
+    Network contains no hidden layers and linear output  layer  with  SOFTMAX-
+    normalization  (so  outputs  sums  up  to  1.0  and  converge to posterior
+    probabilities).
+
+      -- ALGLIB --
+         Copyright 04.11.2007 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlpcreatec0(int nin, int nout, out multilayerperceptron network)
+    {
+        network = new multilayerperceptron();
+        mlpbase.mlpcreatec0(nin, nout, network.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    Same as MLPCreateC0, but with one non-linear hidden layer.
+
+      -- ALGLIB --
+         Copyright 04.11.2007 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlpcreatec1(int nin, int nhid, int nout, out multilayerperceptron network)
+    {
+        network = new multilayerperceptron();
+        mlpbase.mlpcreatec1(nin, nhid, nout, network.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    Same as MLPCreateC0, but with two non-linear hidden layers.
+
+      -- ALGLIB --
+         Copyright 04.11.2007 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlpcreatec2(int nin, int nhid1, int nhid2, int nout, out multilayerperceptron network)
+    {
+        network = new multilayerperceptron();
+        mlpbase.mlpcreatec2(nin, nhid1, nhid2, nout, network.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    Randomization of neural network weights
+
+      -- ALGLIB --
+         Copyright 06.11.2007 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlprandomize(multilayerperceptron network)
+    {
+
+        mlpbase.mlprandomize(network.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    Randomization of neural network weights and standartisator
+
+      -- ALGLIB --
+         Copyright 10.03.2008 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlprandomizefull(multilayerperceptron network)
+    {
+
+        mlpbase.mlprandomizefull(network.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    Returns information about initialized network: number of inputs, outputs,
+    weights.
+
+      -- ALGLIB --
+         Copyright 04.11.2007 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlpproperties(multilayerperceptron network, out int nin, out int nout, out int wcount)
+    {
+        nin = 0;
+        nout = 0;
+        wcount = 0;
+        mlpbase.mlpproperties(network.innerobj, ref nin, ref nout, ref wcount);
+        return;
+    }
+
+    /*************************************************************************
+    Returns number of inputs.
+
+      -- ALGLIB --
+         Copyright 19.10.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static int mlpgetinputscount(multilayerperceptron network)
+    {
+
+        int result = mlpbase.mlpgetinputscount(network.innerobj);
+        return result;
+    }
+
+    /*************************************************************************
+    Returns number of outputs.
+
+      -- ALGLIB --
+         Copyright 19.10.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static int mlpgetoutputscount(multilayerperceptron network)
+    {
+
+        int result = mlpbase.mlpgetoutputscount(network.innerobj);
+        return result;
+    }
+
+    /*************************************************************************
+    Returns number of weights.
+
+      -- ALGLIB --
+         Copyright 19.10.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static int mlpgetweightscount(multilayerperceptron network)
+    {
+
+        int result = mlpbase.mlpgetweightscount(network.innerobj);
+        return result;
+    }
+
+    /*************************************************************************
+    Tells whether network is SOFTMAX-normalized (i.e. classifier) or not.
+
+      -- ALGLIB --
+         Copyright 04.11.2007 by Bochkanov Sergey
+    *************************************************************************/
+    public static bool mlpissoftmax(multilayerperceptron network)
+    {
+
+        bool result = mlpbase.mlpissoftmax(network.innerobj);
+        return result;
+    }
+
+    /*************************************************************************
+    This function returns total number of layers (including input, hidden and
+    output layers).
+
+      -- ALGLIB --
+         Copyright 25.03.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static int mlpgetlayerscount(multilayerperceptron network)
+    {
+
+        int result = mlpbase.mlpgetlayerscount(network.innerobj);
+        return result;
+    }
+
+    /*************************************************************************
+    This function returns size of K-th layer.
+
+    K=0 corresponds to input layer, K=CNT-1 corresponds to output layer.
+
+    Size of the output layer is always equal to the number of outputs, although
+    when we have softmax-normalized network, last neuron doesn't have any
+    connections - it is just zero.
+
+      -- ALGLIB --
+         Copyright 25.03.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static int mlpgetlayersize(multilayerperceptron network, int k)
+    {
+
+        int result = mlpbase.mlpgetlayersize(network.innerobj, k);
+        return result;
+    }
+
+    /*************************************************************************
+    This function returns offset/scaling coefficients for I-th input of the
+    network.
+
+    INPUT PARAMETERS:
+        Network     -   network
+        I           -   input index
+
+    OUTPUT PARAMETERS:
+        Mean        -   mean term
+        Sigma       -   sigma term, guaranteed to be nonzero.
+
+    I-th input is passed through linear transformation
+        IN[i] = (IN[i]-Mean)/Sigma
+    before feeding to the network
+
+      -- ALGLIB --
+         Copyright 25.03.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlpgetinputscaling(multilayerperceptron network, int i, out double mean, out double sigma)
+    {
+        mean = 0;
+        sigma = 0;
+        mlpbase.mlpgetinputscaling(network.innerobj, i, ref mean, ref sigma);
+        return;
+    }
+
+    /*************************************************************************
+    This function returns offset/scaling coefficients for I-th output of the
+    network.
+
+    INPUT PARAMETERS:
+        Network     -   network
+        I           -   input index
+
+    OUTPUT PARAMETERS:
+        Mean        -   mean term
+        Sigma       -   sigma term, guaranteed to be nonzero.
+
+    I-th output is passed through linear transformation
+        OUT[i] = OUT[i]*Sigma+Mean
+    before returning it to user. In case we have SOFTMAX-normalized network,
+    we return (Mean,Sigma)=(0.0,1.0).
+
+      -- ALGLIB --
+         Copyright 25.03.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlpgetoutputscaling(multilayerperceptron network, int i, out double mean, out double sigma)
+    {
+        mean = 0;
+        sigma = 0;
+        mlpbase.mlpgetoutputscaling(network.innerobj, i, ref mean, ref sigma);
+        return;
+    }
+
+    /*************************************************************************
+    This function returns information about Ith neuron of Kth layer
+
+    INPUT PARAMETERS:
+        Network     -   network
+        K           -   layer index
+        I           -   neuron index (within layer)
+
+    OUTPUT PARAMETERS:
+        FKind       -   activation function type (used by MLPActivationFunction())
+                        this value is zero for input or linear neurons
+        Threshold   -   also called offset, bias
+                        zero for input neurons
+
+    NOTE: this function throws exception if layer or neuron with  given  index
+    do not exists.
+
+      -- ALGLIB --
+         Copyright 25.03.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlpgetneuroninfo(multilayerperceptron network, int k, int i, out int fkind, out double threshold)
+    {
+        fkind = 0;
+        threshold = 0;
+        mlpbase.mlpgetneuroninfo(network.innerobj, k, i, ref fkind, ref threshold);
+        return;
+    }
+
+    /*************************************************************************
+    This function returns information about connection from I0-th neuron of
+    K0-th layer to I1-th neuron of K1-th layer.
+
+    INPUT PARAMETERS:
+        Network     -   network
+        K0          -   layer index
+        I0          -   neuron index (within layer)
+        K1          -   layer index
+        I1          -   neuron index (within layer)
+
+    RESULT:
+        connection weight (zero for non-existent connections)
+
+    This function:
+    1. throws exception if layer or neuron with given index do not exists.
+    2. returns zero if neurons exist, but there is no connection between them
+
+      -- ALGLIB --
+         Copyright 25.03.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static double mlpgetweight(multilayerperceptron network, int k0, int i0, int k1, int i1)
+    {
+
+        double result = mlpbase.mlpgetweight(network.innerobj, k0, i0, k1, i1);
+        return result;
+    }
+
+    /*************************************************************************
+    This function sets offset/scaling coefficients for I-th input of the
+    network.
+
+    INPUT PARAMETERS:
+        Network     -   network
+        I           -   input index
+        Mean        -   mean term
+        Sigma       -   sigma term (if zero, will be replaced by 1.0)
+
+    NTE: I-th input is passed through linear transformation
+        IN[i] = (IN[i]-Mean)/Sigma
+    before feeding to the network. This function sets Mean and Sigma.
+
+      -- ALGLIB --
+         Copyright 25.03.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlpsetinputscaling(multilayerperceptron network, int i, double mean, double sigma)
+    {
+
+        mlpbase.mlpsetinputscaling(network.innerobj, i, mean, sigma);
+        return;
+    }
+
+    /*************************************************************************
+    This function sets offset/scaling coefficients for I-th output of the
+    network.
+
+    INPUT PARAMETERS:
+        Network     -   network
+        I           -   input index
+        Mean        -   mean term
+        Sigma       -   sigma term (if zero, will be replaced by 1.0)
+
+    OUTPUT PARAMETERS:
+
+    NOTE: I-th output is passed through linear transformation
+        OUT[i] = OUT[i]*Sigma+Mean
+    before returning it to user. This function sets Sigma/Mean. In case we
+    have SOFTMAX-normalized network, you can not set (Sigma,Mean) to anything
+    other than(0.0,1.0) - this function will throw exception.
+
+      -- ALGLIB --
+         Copyright 25.03.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlpsetoutputscaling(multilayerperceptron network, int i, double mean, double sigma)
+    {
+
+        mlpbase.mlpsetoutputscaling(network.innerobj, i, mean, sigma);
+        return;
+    }
+
+    /*************************************************************************
+    This function modifies information about Ith neuron of Kth layer
+
+    INPUT PARAMETERS:
+        Network     -   network
+        K           -   layer index
+        I           -   neuron index (within layer)
+        FKind       -   activation function type (used by MLPActivationFunction())
+                        this value must be zero for input neurons
+                        (you can not set activation function for input neurons)
+        Threshold   -   also called offset, bias
+                        this value must be zero for input neurons
+                        (you can not set threshold for input neurons)
+
+    NOTES:
+    1. this function throws exception if layer or neuron with given index do
+       not exists.
+    2. this function also throws exception when you try to set non-linear
+       activation function for input neurons (any kind of network) or for output
+       neurons of classifier network.
+    3. this function throws exception when you try to set non-zero threshold for
+       input neurons (any kind of network).
+
+      -- ALGLIB --
+         Copyright 25.03.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlpsetneuroninfo(multilayerperceptron network, int k, int i, int fkind, double threshold)
+    {
+
+        mlpbase.mlpsetneuroninfo(network.innerobj, k, i, fkind, threshold);
+        return;
+    }
+
+    /*************************************************************************
+    This function modifies information about connection from I0-th neuron of
+    K0-th layer to I1-th neuron of K1-th layer.
+
+    INPUT PARAMETERS:
+        Network     -   network
+        K0          -   layer index
+        I0          -   neuron index (within layer)
+        K1          -   layer index
+        I1          -   neuron index (within layer)
+        W           -   connection weight (must be zero for non-existent
+                        connections)
+
+    This function:
+    1. throws exception if layer or neuron with given index do not exists.
+    2. throws exception if you try to set non-zero weight for non-existent
+       connection
+
+      -- ALGLIB --
+         Copyright 25.03.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlpsetweight(multilayerperceptron network, int k0, int i0, int k1, int i1, double w)
+    {
+
+        mlpbase.mlpsetweight(network.innerobj, k0, i0, k1, i1, w);
+        return;
+    }
+
+    /*************************************************************************
+    Neural network activation function
+
+    INPUT PARAMETERS:
+        NET         -   neuron input
+        K           -   function index (zero for linear function)
+
+    OUTPUT PARAMETERS:
+        F           -   function
+        DF          -   its derivative
+        D2F         -   its second derivative
+
+      -- ALGLIB --
+         Copyright 04.11.2007 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlpactivationfunction(double net, int k, out double f, out double df, out double d2f)
+    {
+        f = 0;
+        df = 0;
+        d2f = 0;
+        mlpbase.mlpactivationfunction(net, k, ref f, ref df, ref d2f);
+        return;
+    }
+
+    /*************************************************************************
+    Procesing
+
+    INPUT PARAMETERS:
+        Network -   neural network
+        X       -   input vector,  array[0..NIn-1].
+
+    OUTPUT PARAMETERS:
+        Y       -   result. Regression estimate when solving regression  task,
+                    vector of posterior probabilities for classification task.
+
+    See also MLPProcessI
+
+      -- ALGLIB --
+         Copyright 04.11.2007 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlpprocess(multilayerperceptron network, double[] x, ref double[] y)
+    {
+
+        mlpbase.mlpprocess(network.innerobj, x, ref y);
+        return;
+    }
+
+    /*************************************************************************
+    'interactive'  variant  of  MLPProcess  for  languages  like  Python which
+    support constructs like "Y = MLPProcess(NN,X)" and interactive mode of the
+    interpreter
+
+    This function allocates new array on each call,  so  it  is  significantly
+    slower than its 'non-interactive' counterpart, but it is  more  convenient
+    when you call it from command line.
+
+      -- ALGLIB --
+         Copyright 21.09.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlpprocessi(multilayerperceptron network, double[] x, out double[] y)
+    {
+        y = new double[0];
+        mlpbase.mlpprocessi(network.innerobj, x, ref y);
+        return;
+    }
+
+    /*************************************************************************
+    Error function for neural network, internal subroutine.
+
+      -- ALGLIB --
+         Copyright 04.11.2007 by Bochkanov Sergey
+    *************************************************************************/
+    public static double mlperror(multilayerperceptron network, double[,] xy, int ssize)
+    {
+
+        double result = mlpbase.mlperror(network.innerobj, xy, ssize);
+        return result;
+    }
+
+    /*************************************************************************
+    Natural error function for neural network, internal subroutine.
+
+      -- ALGLIB --
+         Copyright 04.11.2007 by Bochkanov Sergey
+    *************************************************************************/
+    public static double mlperrorn(multilayerperceptron network, double[,] xy, int ssize)
+    {
+
+        double result = mlpbase.mlperrorn(network.innerobj, xy, ssize);
+        return result;
+    }
+
+    /*************************************************************************
+    Classification error
+
+      -- ALGLIB --
+         Copyright 04.11.2007 by Bochkanov Sergey
+    *************************************************************************/
+    public static int mlpclserror(multilayerperceptron network, double[,] xy, int ssize)
+    {
+
+        int result = mlpbase.mlpclserror(network.innerobj, xy, ssize);
+        return result;
+    }
+
+    /*************************************************************************
+    Relative classification error on the test set
+
+    INPUT PARAMETERS:
+        Network -   network
+        XY      -   test set
+        NPoints -   test set size
+
+    RESULT:
+        percent of incorrectly classified cases. Works both for
+        classifier networks and general purpose networks used as
+        classifiers.
+
+      -- ALGLIB --
+         Copyright 25.12.2008 by Bochkanov Sergey
+    *************************************************************************/
+    public static double mlprelclserror(multilayerperceptron network, double[,] xy, int npoints)
+    {
+
+        double result = mlpbase.mlprelclserror(network.innerobj, xy, npoints);
+        return result;
+    }
+
+    /*************************************************************************
+    Average cross-entropy (in bits per element) on the test set
+
+    INPUT PARAMETERS:
+        Network -   neural network
+        XY      -   test set
+        NPoints -   test set size
+
+    RESULT:
+        CrossEntropy/(NPoints*LN(2)).
+        Zero if network solves regression task.
+
+      -- ALGLIB --
+         Copyright 08.01.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static double mlpavgce(multilayerperceptron network, double[,] xy, int npoints)
+    {
+
+        double result = mlpbase.mlpavgce(network.innerobj, xy, npoints);
+        return result;
+    }
+
+    /*************************************************************************
+    RMS error on the test set
+
+    INPUT PARAMETERS:
+        Network -   neural network
+        XY      -   test set
+        NPoints -   test set size
+
+    RESULT:
+        root mean square error.
+        Its meaning for regression task is obvious. As for
+        classification task, RMS error means error when estimating posterior
+        probabilities.
+
+      -- ALGLIB --
+         Copyright 04.11.2007 by Bochkanov Sergey
+    *************************************************************************/
+    public static double mlprmserror(multilayerperceptron network, double[,] xy, int npoints)
+    {
+
+        double result = mlpbase.mlprmserror(network.innerobj, xy, npoints);
+        return result;
+    }
+
+    /*************************************************************************
+    Average error on the test set
+
+    INPUT PARAMETERS:
+        Network -   neural network
+        XY      -   test set
+        NPoints -   test set size
+
+    RESULT:
+        Its meaning for regression task is obvious. As for
+        classification task, it means average error when estimating posterior
+        probabilities.
+
+      -- ALGLIB --
+         Copyright 11.03.2008 by Bochkanov Sergey
+    *************************************************************************/
+    public static double mlpavgerror(multilayerperceptron network, double[,] xy, int npoints)
+    {
+
+        double result = mlpbase.mlpavgerror(network.innerobj, xy, npoints);
+        return result;
+    }
+
+    /*************************************************************************
+    Average relative error on the test set
+
+    INPUT PARAMETERS:
+        Network -   neural network
+        XY      -   test set
+        NPoints -   test set size
+
+    RESULT:
+        Its meaning for regression task is obvious. As for
+        classification task, it means average relative error when estimating
+        posterior probability of belonging to the correct class.
+
+      -- ALGLIB --
+         Copyright 11.03.2008 by Bochkanov Sergey
+    *************************************************************************/
+    public static double mlpavgrelerror(multilayerperceptron network, double[,] xy, int npoints)
+    {
+
+        double result = mlpbase.mlpavgrelerror(network.innerobj, xy, npoints);
+        return result;
+    }
+
+    /*************************************************************************
+    Gradient calculation
+
+    INPUT PARAMETERS:
+        Network -   network initialized with one of the network creation funcs
+        X       -   input vector, length of array must be at least NIn
+        DesiredY-   desired outputs, length of array must be at least NOut
+        Grad    -   possibly preallocated array. If size of array is smaller
+                    than WCount, it will be reallocated. It is recommended to
+                    reuse previously allocated array to reduce allocation
+                    overhead.
+
+    OUTPUT PARAMETERS:
+        E       -   error function, SUM(sqr(y[i]-desiredy[i])/2,i)
+        Grad    -   gradient of E with respect to weights of network, array[WCount]
+
+      -- ALGLIB --
+         Copyright 04.11.2007 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlpgrad(multilayerperceptron network, double[] x, double[] desiredy, out double e, ref double[] grad)
+    {
+        e = 0;
+        mlpbase.mlpgrad(network.innerobj, x, desiredy, ref e, ref grad);
+        return;
+    }
+
+    /*************************************************************************
+    Gradient calculation (natural error function is used)
+
+    INPUT PARAMETERS:
+        Network -   network initialized with one of the network creation funcs
+        X       -   input vector, length of array must be at least NIn
+        DesiredY-   desired outputs, length of array must be at least NOut
+        Grad    -   possibly preallocated array. If size of array is smaller
+                    than WCount, it will be reallocated. It is recommended to
+                    reuse previously allocated array to reduce allocation
+                    overhead.
+
+    OUTPUT PARAMETERS:
+        E       -   error function, sum-of-squares for regression networks,
+                    cross-entropy for classification networks.
+        Grad    -   gradient of E with respect to weights of network, array[WCount]
+
+      -- ALGLIB --
+         Copyright 04.11.2007 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlpgradn(multilayerperceptron network, double[] x, double[] desiredy, out double e, ref double[] grad)
+    {
+        e = 0;
+        mlpbase.mlpgradn(network.innerobj, x, desiredy, ref e, ref grad);
+        return;
+    }
+
+    /*************************************************************************
+    Batch gradient calculation for a set of inputs/outputs
+
+    INPUT PARAMETERS:
+        Network -   network initialized with one of the network creation funcs
+        XY      -   set of inputs/outputs; one sample = one row;
+                    first NIn columns contain inputs,
+                    next NOut columns - desired outputs.
+        SSize   -   number of elements in XY
+        Grad    -   possibly preallocated array. If size of array is smaller
+                    than WCount, it will be reallocated. It is recommended to
+                    reuse previously allocated array to reduce allocation
+                    overhead.
+
+    OUTPUT PARAMETERS:
+        E       -   error function, SUM(sqr(y[i]-desiredy[i])/2,i)
+        Grad    -   gradient of E with respect to weights of network, array[WCount]
+
+      -- ALGLIB --
+         Copyright 04.11.2007 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlpgradbatch(multilayerperceptron network, double[,] xy, int ssize, out double e, ref double[] grad)
+    {
+        e = 0;
+        mlpbase.mlpgradbatch(network.innerobj, xy, ssize, ref e, ref grad);
+        return;
+    }
+
+    /*************************************************************************
+    Batch gradient calculation for a set of inputs/outputs
+    (natural error function is used)
+
+    INPUT PARAMETERS:
+        Network -   network initialized with one of the network creation funcs
+        XY      -   set of inputs/outputs; one sample = one row;
+                    first NIn columns contain inputs,
+                    next NOut columns - desired outputs.
+        SSize   -   number of elements in XY
+        Grad    -   possibly preallocated array. If size of array is smaller
+                    than WCount, it will be reallocated. It is recommended to
+                    reuse previously allocated array to reduce allocation
+                    overhead.
+
+    OUTPUT PARAMETERS:
+        E       -   error function, sum-of-squares for regression networks,
+                    cross-entropy for classification networks.
+        Grad    -   gradient of E with respect to weights of network, array[WCount]
+
+      -- ALGLIB --
+         Copyright 04.11.2007 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlpgradnbatch(multilayerperceptron network, double[,] xy, int ssize, out double e, ref double[] grad)
+    {
+        e = 0;
+        mlpbase.mlpgradnbatch(network.innerobj, xy, ssize, ref e, ref grad);
+        return;
+    }
+
+    /*************************************************************************
+    Batch Hessian calculation (natural error function) using R-algorithm.
+    Internal subroutine.
+
+      -- ALGLIB --
+         Copyright 26.01.2008 by Bochkanov Sergey.
+
+         Hessian calculation based on R-algorithm described in
+         "Fast Exact Multiplication by the Hessian",
+         B. A. Pearlmutter,
+         Neural Computation, 1994.
+    *************************************************************************/
+    public static void mlphessiannbatch(multilayerperceptron network, double[,] xy, int ssize, out double e, ref double[] grad, ref double[,] h)
+    {
+        e = 0;
+        mlpbase.mlphessiannbatch(network.innerobj, xy, ssize, ref e, ref grad, ref h);
+        return;
+    }
+
+    /*************************************************************************
+    Batch Hessian calculation using R-algorithm.
+    Internal subroutine.
+
+      -- ALGLIB --
+         Copyright 26.01.2008 by Bochkanov Sergey.
+
+         Hessian calculation based on R-algorithm described in
+         "Fast Exact Multiplication by the Hessian",
+         B. A. Pearlmutter,
+         Neural Computation, 1994.
+    *************************************************************************/
+    public static void mlphessianbatch(multilayerperceptron network, double[,] xy, int ssize, out double e, ref double[] grad, ref double[,] h)
+    {
+        e = 0;
+        mlpbase.mlphessianbatch(network.innerobj, xy, ssize, ref e, ref grad, ref h);
+        return;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+
+    *************************************************************************/
+    public class logitmodel
+    {
+        //
+        // Public declarations
+        //
+
+        public logitmodel()
+        {
+            _innerobj = new logit.logitmodel();
+        }
+
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private logit.logitmodel _innerobj;
+        public logit.logitmodel innerobj { get { return _innerobj; } }
+        public logitmodel(logit.logitmodel obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+
+    /*************************************************************************
+    MNLReport structure contains information about training process:
+    * NGrad     -   number of gradient calculations
+    * NHess     -   number of Hessian calculations
+    *************************************************************************/
+    public class mnlreport
+    {
+        //
+        // Public declarations
+        //
+        public int ngrad { get { return _innerobj.ngrad; } set { _innerobj.ngrad = value; } }
+        public int nhess { get { return _innerobj.nhess; } set { _innerobj.nhess = value; } }
+
+        public mnlreport()
+        {
+            _innerobj = new logit.mnlreport();
+        }
+
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private logit.mnlreport _innerobj;
+        public logit.mnlreport innerobj { get { return _innerobj; } }
+        public mnlreport(logit.mnlreport obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+    /*************************************************************************
+    This subroutine trains logit model.
+
+    INPUT PARAMETERS:
+        XY          -   training set, array[0..NPoints-1,0..NVars]
+                        First NVars columns store values of independent
+                        variables, next column stores number of class (from 0
+                        to NClasses-1) which dataset element belongs to. Fractional
+                        values are rounded to nearest integer.
+        NPoints     -   training set size, NPoints>=1
+        NVars       -   number of independent variables, NVars>=1
+        NClasses    -   number of classes, NClasses>=2
+
+    OUTPUT PARAMETERS:
+        Info        -   return code:
+                        * -2, if there is a point with class number
+                              outside of [0..NClasses-1].
+                        * -1, if incorrect parameters was passed
+                              (NPoints<NVars+2, NVars<1, NClasses<2).
+                        *  1, if task has been solved
+        LM          -   model built
+        Rep         -   training report
+
+      -- ALGLIB --
+         Copyright 10.09.2008 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mnltrainh(double[,] xy, int npoints, int nvars, int nclasses, out int info, out logitmodel lm, out mnlreport rep)
+    {
+        info = 0;
+        lm = new logitmodel();
+        rep = new mnlreport();
+        logit.mnltrainh(xy, npoints, nvars, nclasses, ref info, lm.innerobj, rep.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    Procesing
+
+    INPUT PARAMETERS:
+        LM      -   logit model, passed by non-constant reference
+                    (some fields of structure are used as temporaries
+                    when calculating model output).
+        X       -   input vector,  array[0..NVars-1].
+        Y       -   (possibly) preallocated buffer; if size of Y is less than
+                    NClasses, it will be reallocated.If it is large enough, it
+                    is NOT reallocated, so we can save some time on reallocation.
+
+    OUTPUT PARAMETERS:
+        Y       -   result, array[0..NClasses-1]
+                    Vector of posterior probabilities for classification task.
+
+      -- ALGLIB --
+         Copyright 10.09.2008 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mnlprocess(logitmodel lm, double[] x, ref double[] y)
+    {
+
+        logit.mnlprocess(lm.innerobj, x, ref y);
+        return;
+    }
+
+    /*************************************************************************
+    'interactive'  variant  of  MNLProcess  for  languages  like  Python which
+    support constructs like "Y = MNLProcess(LM,X)" and interactive mode of the
+    interpreter
+
+    This function allocates new array on each call,  so  it  is  significantly
+    slower than its 'non-interactive' counterpart, but it is  more  convenient
+    when you call it from command line.
+
+      -- ALGLIB --
+         Copyright 10.09.2008 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mnlprocessi(logitmodel lm, double[] x, out double[] y)
+    {
+        y = new double[0];
+        logit.mnlprocessi(lm.innerobj, x, ref y);
+        return;
+    }
+
+    /*************************************************************************
+    Unpacks coefficients of logit model. Logit model have form:
+
+        P(class=i) = S(i) / (S(0) + S(1) + ... +S(M-1))
+              S(i) = Exp(A[i,0]*X[0] + ... + A[i,N-1]*X[N-1] + A[i,N]), when i<M-1
+            S(M-1) = 1
+
+    INPUT PARAMETERS:
+        LM          -   logit model in ALGLIB format
+
+    OUTPUT PARAMETERS:
+        V           -   coefficients, array[0..NClasses-2,0..NVars]
+        NVars       -   number of independent variables
+        NClasses    -   number of classes
+
+      -- ALGLIB --
+         Copyright 10.09.2008 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mnlunpack(logitmodel lm, out double[,] a, out int nvars, out int nclasses)
+    {
+        a = new double[0,0];
+        nvars = 0;
+        nclasses = 0;
+        logit.mnlunpack(lm.innerobj, ref a, ref nvars, ref nclasses);
+        return;
+    }
+
+    /*************************************************************************
+    "Packs" coefficients and creates logit model in ALGLIB format (MNLUnpack
+    reversed).
+
+    INPUT PARAMETERS:
+        A           -   model (see MNLUnpack)
+        NVars       -   number of independent variables
+        NClasses    -   number of classes
+
+    OUTPUT PARAMETERS:
+        LM          -   logit model.
+
+      -- ALGLIB --
+         Copyright 10.09.2008 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mnlpack(double[,] a, int nvars, int nclasses, out logitmodel lm)
+    {
+        lm = new logitmodel();
+        logit.mnlpack(a, nvars, nclasses, lm.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    Average cross-entropy (in bits per element) on the test set
+
+    INPUT PARAMETERS:
+        LM      -   logit model
+        XY      -   test set
+        NPoints -   test set size
+
+    RESULT:
+        CrossEntropy/(NPoints*ln(2)).
+
+      -- ALGLIB --
+         Copyright 10.09.2008 by Bochkanov Sergey
+    *************************************************************************/
+    public static double mnlavgce(logitmodel lm, double[,] xy, int npoints)
+    {
+
+        double result = logit.mnlavgce(lm.innerobj, xy, npoints);
+        return result;
+    }
+
+    /*************************************************************************
+    Relative classification error on the test set
+
+    INPUT PARAMETERS:
+        LM      -   logit model
+        XY      -   test set
+        NPoints -   test set size
+
+    RESULT:
+        percent of incorrectly classified cases.
+
+      -- ALGLIB --
+         Copyright 10.09.2008 by Bochkanov Sergey
+    *************************************************************************/
+    public static double mnlrelclserror(logitmodel lm, double[,] xy, int npoints)
+    {
+
+        double result = logit.mnlrelclserror(lm.innerobj, xy, npoints);
+        return result;
+    }
+
+    /*************************************************************************
+    RMS error on the test set
+
+    INPUT PARAMETERS:
+        LM      -   logit model
+        XY      -   test set
+        NPoints -   test set size
+
+    RESULT:
+        root mean square error (error when estimating posterior probabilities).
+
+      -- ALGLIB --
+         Copyright 30.08.2008 by Bochkanov Sergey
+    *************************************************************************/
+    public static double mnlrmserror(logitmodel lm, double[,] xy, int npoints)
+    {
+
+        double result = logit.mnlrmserror(lm.innerobj, xy, npoints);
+        return result;
+    }
+
+    /*************************************************************************
+    Average error on the test set
+
+    INPUT PARAMETERS:
+        LM      -   logit model
+        XY      -   test set
+        NPoints -   test set size
+
+    RESULT:
+        average error (error when estimating posterior probabilities).
+
+      -- ALGLIB --
+         Copyright 30.08.2008 by Bochkanov Sergey
+    *************************************************************************/
+    public static double mnlavgerror(logitmodel lm, double[,] xy, int npoints)
+    {
+
+        double result = logit.mnlavgerror(lm.innerobj, xy, npoints);
+        return result;
+    }
+
+    /*************************************************************************
+    Average relative error on the test set
+
+    INPUT PARAMETERS:
+        LM      -   logit model
+        XY      -   test set
+        NPoints -   test set size
+
+    RESULT:
+        average relative error (error when estimating posterior probabilities).
+
+      -- ALGLIB --
+         Copyright 30.08.2008 by Bochkanov Sergey
+    *************************************************************************/
+    public static double mnlavgrelerror(logitmodel lm, double[,] xy, int ssize)
+    {
+
+        double result = logit.mnlavgrelerror(lm.innerobj, xy, ssize);
+        return result;
+    }
+
+    /*************************************************************************
+    Classification error on test set = MNLRelClsError*NPoints
+
+      -- ALGLIB --
+         Copyright 10.09.2008 by Bochkanov Sergey
+    *************************************************************************/
+    public static int mnlclserror(logitmodel lm, double[,] xy, int npoints)
+    {
+
+        int result = logit.mnlclserror(lm.innerobj, xy, npoints);
+        return result;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    This structure is a MCPD (Markov Chains for Population Data) solver.
+
+    You should use ALGLIB functions in order to work with this object.
+
+      -- ALGLIB --
+         Copyright 23.05.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public class mcpdstate
+    {
+        //
+        // Public declarations
+        //
+
+        public mcpdstate()
+        {
+            _innerobj = new mcpd.mcpdstate();
+        }
+
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private mcpd.mcpdstate _innerobj;
+        public mcpd.mcpdstate innerobj { get { return _innerobj; } }
+        public mcpdstate(mcpd.mcpdstate obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+
+    /*************************************************************************
+    This structure is a MCPD training report:
+        InnerIterationsCount    -   number of inner iterations of the
+                                    underlying optimization algorithm
+        OuterIterationsCount    -   number of outer iterations of the
+                                    underlying optimization algorithm
+        NFEV                    -   number of merit function evaluations
+        TerminationType         -   termination type
+                                    (same as for MinBLEIC optimizer, positive
+                                    values denote success, negative ones -
+                                    failure)
+
+      -- ALGLIB --
+         Copyright 23.05.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public class mcpdreport
+    {
+        //
+        // Public declarations
+        //
+        public int inneriterationscount { get { return _innerobj.inneriterationscount; } set { _innerobj.inneriterationscount = value; } }
+        public int outeriterationscount { get { return _innerobj.outeriterationscount; } set { _innerobj.outeriterationscount = value; } }
+        public int nfev { get { return _innerobj.nfev; } set { _innerobj.nfev = value; } }
+        public int terminationtype { get { return _innerobj.terminationtype; } set { _innerobj.terminationtype = value; } }
+
+        public mcpdreport()
+        {
+            _innerobj = new mcpd.mcpdreport();
+        }
+
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private mcpd.mcpdreport _innerobj;
+        public mcpd.mcpdreport innerobj { get { return _innerobj; } }
+        public mcpdreport(mcpd.mcpdreport obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+    /*************************************************************************
+    DESCRIPTION:
+
+    This function creates MCPD (Markov Chains for Population Data) solver.
+
+    This  solver  can  be  used  to find transition matrix P for N-dimensional
+    prediction  problem  where transition from X[i] to X[i+1] is  modelled  as
+        X[i+1] = P*X[i]
+    where X[i] and X[i+1] are N-dimensional population vectors (components  of
+    each X are non-negative), and P is a N*N transition matrix (elements of  P
+    are non-negative, each column sums to 1.0).
+
+    Such models arise when when:
+    * there is some population of individuals
+    * individuals can have different states
+    * individuals can transit from one state to another
+    * population size is constant, i.e. there is no new individuals and no one
+      leaves population
+    * you want to model transitions of individuals from one state into another
+
+    USAGE:
+
+    Here we give very brief outline of the MCPD. We strongly recommend you  to
+    read examples in the ALGLIB Reference Manual and to read ALGLIB User Guide
+    on data analysis which is available at http://www.alglib.net/dataanalysis/
+
+    1. User initializes algorithm state with MCPDCreate() call
+
+    2. User  adds  one  or  more  tracks -  sequences of states which describe
+       evolution of a system being modelled from different starting conditions
+
+    3. User may add optional boundary, equality  and/or  linear constraints on
+       the coefficients of P by calling one of the following functions:
+       * MCPDSetEC() to set equality constraints
+       * MCPDSetBC() to set bound constraints
+       * MCPDSetLC() to set linear constraints
+
+    4. Optionally,  user  may  set  custom  weights  for prediction errors (by
+       default, algorithm assigns non-equal, automatically chosen weights  for
+       errors in the prediction of different components of X). It can be  done
+       with a call of MCPDSetPredictionWeights() function.
+
+    5. User calls MCPDSolve() function which takes algorithm  state and
+       pointer (delegate, etc.) to callback function which calculates F/G.
+
+    6. User calls MCPDResults() to get solution
+
+    INPUT PARAMETERS:
+        N       -   problem dimension, N>=1
+
+    OUTPUT PARAMETERS:
+        State   -   structure stores algorithm state
+
+      -- ALGLIB --
+         Copyright 23.05.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mcpdcreate(int n, out mcpdstate s)
+    {
+        s = new mcpdstate();
+        mcpd.mcpdcreate(n, s.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    DESCRIPTION:
+
+    This function is a specialized version of MCPDCreate()  function,  and  we
+    recommend  you  to read comments for this function for general information
+    about MCPD solver.
+
+    This  function  creates  MCPD (Markov Chains for Population  Data)  solver
+    for "Entry-state" model,  i.e. model  where transition from X[i] to X[i+1]
+    is modelled as
+        X[i+1] = P*X[i]
+    where
+        X[i] and X[i+1] are N-dimensional state vectors
+        P is a N*N transition matrix
+    and  one  selected component of X[] is called "entry" state and is treated
+    in a special way:
+        system state always transits from "entry" state to some another state
+        system state can not transit from any state into "entry" state
+    Such conditions basically mean that row of P which corresponds to  "entry"
+    state is zero.
+
+    Such models arise when:
+    * there is some population of individuals
+    * individuals can have different states
+    * individuals can transit from one state to another
+    * population size is NOT constant -  at every moment of time there is some
+      (unpredictable) amount of "new" individuals, which can transit into  one
+      of the states at the next turn, but still no one leaves population
+    * you want to model transitions of individuals from one state into another
+    * but you do NOT want to predict amount of "new"  individuals  because  it
+      does not depends on individuals already present (hence  system  can  not
+      transit INTO entry state - it can only transit FROM it).
+
+    This model is discussed  in  more  details  in  the ALGLIB User Guide (see
+    http://www.alglib.net/dataanalysis/ for more data).
+
+    INPUT PARAMETERS:
+        N       -   problem dimension, N>=2
+        EntryState- index of entry state, in 0..N-1
+
+    OUTPUT PARAMETERS:
+        State   -   structure stores algorithm state
+
+      -- ALGLIB --
+         Copyright 23.05.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mcpdcreateentry(int n, int entrystate, out mcpdstate s)
+    {
+        s = new mcpdstate();
+        mcpd.mcpdcreateentry(n, entrystate, s.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    DESCRIPTION:
+
+    This function is a specialized version of MCPDCreate()  function,  and  we
+    recommend  you  to read comments for this function for general information
+    about MCPD solver.
+
+    This  function  creates  MCPD (Markov Chains for Population  Data)  solver
+    for "Exit-state" model,  i.e. model  where  transition from X[i] to X[i+1]
+    is modelled as
+        X[i+1] = P*X[i]
+    where
+        X[i] and X[i+1] are N-dimensional state vectors
+        P is a N*N transition matrix
+    and  one  selected component of X[] is called "exit"  state and is treated
+    in a special way:
+        system state can transit from any state into "exit" state
+        system state can not transit from "exit" state into any other state
+        transition operator discards "exit" state (makes it zero at each turn)
+    Such  conditions  basically  mean  that  column  of P which corresponds to
+    "exit" state is zero. Multiplication by such P may decrease sum of  vector
+    components.
+
+    Such models arise when:
+    * there is some population of individuals
+    * individuals can have different states
+    * individuals can transit from one state to another
+    * population size is NOT constant - individuals can move into "exit" state
+      and leave population at the next turn, but there are no new individuals
+    * amount of individuals which leave population can be predicted
+    * you want to model transitions of individuals from one state into another
+      (including transitions into the "exit" state)
+
+    This model is discussed  in  more  details  in  the ALGLIB User Guide (see
+    http://www.alglib.net/dataanalysis/ for more data).
+
+    INPUT PARAMETERS:
+        N       -   problem dimension, N>=2
+        ExitState-  index of exit state, in 0..N-1
+
+    OUTPUT PARAMETERS:
+        State   -   structure stores algorithm state
+
+      -- ALGLIB --
+         Copyright 23.05.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mcpdcreateexit(int n, int exitstate, out mcpdstate s)
+    {
+        s = new mcpdstate();
+        mcpd.mcpdcreateexit(n, exitstate, s.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    DESCRIPTION:
+
+    This function is a specialized version of MCPDCreate()  function,  and  we
+    recommend  you  to read comments for this function for general information
+    about MCPD solver.
+
+    This  function  creates  MCPD (Markov Chains for Population  Data)  solver
+    for "Entry-Exit-states" model, i.e. model where  transition  from  X[i] to
+    X[i+1] is modelled as
+        X[i+1] = P*X[i]
+    where
+        X[i] and X[i+1] are N-dimensional state vectors
+        P is a N*N transition matrix
+    one selected component of X[] is called "entry" state and is treated in  a
+    special way:
+        system state always transits from "entry" state to some another state
+        system state can not transit from any state into "entry" state
+    and another one component of X[] is called "exit" state and is treated  in
+    a special way too:
+        system state can transit from any state into "exit" state
+        system state can not transit from "exit" state into any other state
+        transition operator discards "exit" state (makes it zero at each turn)
+    Such conditions basically mean that:
+        row of P which corresponds to "entry" state is zero
+        column of P which corresponds to "exit" state is zero
+    Multiplication by such P may decrease sum of vector components.
+
+    Such models arise when:
+    * there is some population of individuals
+    * individuals can have different states
+    * individuals can transit from one state to another
+    * population size is NOT constant
+    * at every moment of time there is some (unpredictable)  amount  of  "new"
+      individuals, which can transit into one of the states at the next turn
+    * some  individuals  can  move  (predictably)  into "exit" state and leave
+      population at the next turn
+    * you want to model transitions of individuals from one state into another,
+      including transitions from the "entry" state and into the "exit" state.
+    * but you do NOT want to predict amount of "new"  individuals  because  it
+      does not depends on individuals already present (hence  system  can  not
+      transit INTO entry state - it can only transit FROM it).
+
+    This model is discussed  in  more  details  in  the ALGLIB User Guide (see
+    http://www.alglib.net/dataanalysis/ for more data).
+
+    INPUT PARAMETERS:
+        N       -   problem dimension, N>=2
+        EntryState- index of entry state, in 0..N-1
+        ExitState-  index of exit state, in 0..N-1
+
+    OUTPUT PARAMETERS:
+        State   -   structure stores algorithm state
+
+      -- ALGLIB --
+         Copyright 23.05.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mcpdcreateentryexit(int n, int entrystate, int exitstate, out mcpdstate s)
+    {
+        s = new mcpdstate();
+        mcpd.mcpdcreateentryexit(n, entrystate, exitstate, s.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    This  function  is  used to add a track - sequence of system states at the
+    different moments of its evolution.
+
+    You  may  add  one  or several tracks to the MCPD solver. In case you have
+    several tracks, they won't overwrite each other. For example,  if you pass
+    two tracks, A1-A2-A3 (system at t=A+1, t=A+2 and t=A+3) and B1-B2-B3, then
+    solver will try to model transitions from t=A+1 to t=A+2, t=A+2 to  t=A+3,
+    t=B+1 to t=B+2, t=B+2 to t=B+3. But it WONT mix these two tracks - i.e. it
+    wont try to model transition from t=A+3 to t=B+1.
+
+    INPUT PARAMETERS:
+        S       -   solver
+        XY      -   track, array[K,N]:
+                    * I-th row is a state at t=I
+                    * elements of XY must be non-negative (exception will be
+                      thrown on negative elements)
+        K       -   number of points in a track
+                    * if given, only leading K rows of XY are used
+                    * if not given, automatically determined from size of XY
+
+    NOTES:
+
+    1. Track may contain either proportional or population data:
+       * with proportional data all rows of XY must sum to 1.0, i.e. we have
+         proportions instead of absolute population values
+       * with population data rows of XY contain population counts and generally
+         do not sum to 1.0 (although they still must be non-negative)
+
+      -- ALGLIB --
+         Copyright 23.05.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mcpdaddtrack(mcpdstate s, double[,] xy, int k)
+    {
+
+        mcpd.mcpdaddtrack(s.innerobj, xy, k);
+        return;
+    }
+    public static void mcpdaddtrack(mcpdstate s, double[,] xy)
+    {
+        int k;
+
+
+        k = ap.rows(xy);
+        mcpd.mcpdaddtrack(s.innerobj, xy, k);
+
+        return;
+    }
+
+    /*************************************************************************
+    This function is used to add equality constraints on the elements  of  the
+    transition matrix P.
+
+    MCPD solver has four types of constraints which can be placed on P:
+    * user-specified equality constraints (optional)
+    * user-specified bound constraints (optional)
+    * user-specified general linear constraints (optional)
+    * basic constraints (always present):
+      * non-negativity: P[i,j]>=0
+      * consistency: every column of P sums to 1.0
+
+    Final  constraints  which  are  passed  to  the  underlying  optimizer are
+    calculated  as  intersection  of all present constraints. For example, you
+    may specify boundary constraint on P[0,0] and equality one:
+        0.1<=P[0,0]<=0.9
+        P[0,0]=0.5
+    Such  combination  of  constraints  will  be  silently  reduced  to  their
+    intersection, which is P[0,0]=0.5.
+
+    This  function  can  be  used  to  place equality constraints on arbitrary
+    subset of elements of P. Set of constraints is specified by EC, which  may
+    contain either NAN's or finite numbers from [0,1]. NAN denotes absence  of
+    constraint, finite number denotes equality constraint on specific  element
+    of P.
+
+    You can also  use  MCPDAddEC()  function  which  allows  to  ADD  equality
+    constraint  for  one  element  of P without changing constraints for other
+    elements.
+
+    These functions (MCPDSetEC and MCPDAddEC) interact as follows:
+    * there is internal matrix of equality constraints which is stored in  the
+      MCPD solver
+    * MCPDSetEC() replaces this matrix by another one (SET)
+    * MCPDAddEC() modifies one element of this matrix and  leaves  other  ones
+      unchanged (ADD)
+    * thus  MCPDAddEC()  call  preserves  all  modifications  done by previous
+      calls,  while  MCPDSetEC()  completely discards all changes  done to the
+      equality constraints.
+
+    INPUT PARAMETERS:
+        S       -   solver
+        EC      -   equality constraints, array[N,N]. Elements of  EC  can  be
+                    either NAN's or finite  numbers from  [0,1].  NAN  denotes
+                    absence  of  constraints,  while  finite  value    denotes
+                    equality constraint on the corresponding element of P.
+
+    NOTES:
+
+    1. infinite values of EC will lead to exception being thrown. Values  less
+    than 0.0 or greater than 1.0 will lead to error code being returned  after
+    call to MCPDSolve().
+
+      -- ALGLIB --
+         Copyright 23.05.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mcpdsetec(mcpdstate s, double[,] ec)
+    {
+
+        mcpd.mcpdsetec(s.innerobj, ec);
+        return;
+    }
+
+    /*************************************************************************
+    This function is used to add equality constraints on the elements  of  the
+    transition matrix P.
+
+    MCPD solver has four types of constraints which can be placed on P:
+    * user-specified equality constraints (optional)
+    * user-specified bound constraints (optional)
+    * user-specified general linear constraints (optional)
+    * basic constraints (always present):
+      * non-negativity: P[i,j]>=0
+      * consistency: every column of P sums to 1.0
+
+    Final  constraints  which  are  passed  to  the  underlying  optimizer are
+    calculated  as  intersection  of all present constraints. For example, you
+    may specify boundary constraint on P[0,0] and equality one:
+        0.1<=P[0,0]<=0.9
+        P[0,0]=0.5
+    Such  combination  of  constraints  will  be  silently  reduced  to  their
+    intersection, which is P[0,0]=0.5.
+
+    This function can be used to ADD equality constraint for one element of  P
+    without changing constraints for other elements.
+
+    You  can  also  use  MCPDSetEC()  function  which  allows  you  to specify
+    arbitrary set of equality constraints in one call.
+
+    These functions (MCPDSetEC and MCPDAddEC) interact as follows:
+    * there is internal matrix of equality constraints which is stored in the
+      MCPD solver
+    * MCPDSetEC() replaces this matrix by another one (SET)
+    * MCPDAddEC() modifies one element of this matrix and leaves  other  ones
+      unchanged (ADD)
+    * thus  MCPDAddEC()  call  preserves  all  modifications done by previous
+      calls,  while  MCPDSetEC()  completely discards all changes done to the
+      equality constraints.
+
+    INPUT PARAMETERS:
+        S       -   solver
+        I       -   row index of element being constrained
+        J       -   column index of element being constrained
+        C       -   value (constraint for P[I,J]).  Can  be  either  NAN  (no
+                    constraint) or finite value from [0,1].
+
+    NOTES:
+
+    1. infinite values of C  will lead to exception being thrown. Values  less
+    than 0.0 or greater than 1.0 will lead to error code being returned  after
+    call to MCPDSolve().
+
+      -- ALGLIB --
+         Copyright 23.05.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mcpdaddec(mcpdstate s, int i, int j, double c)
+    {
+
+        mcpd.mcpdaddec(s.innerobj, i, j, c);
+        return;
+    }
+
+    /*************************************************************************
+    This function is used to add bound constraints  on  the  elements  of  the
+    transition matrix P.
+
+    MCPD solver has four types of constraints which can be placed on P:
+    * user-specified equality constraints (optional)
+    * user-specified bound constraints (optional)
+    * user-specified general linear constraints (optional)
+    * basic constraints (always present):
+      * non-negativity: P[i,j]>=0
+      * consistency: every column of P sums to 1.0
+
+    Final  constraints  which  are  passed  to  the  underlying  optimizer are
+    calculated  as  intersection  of all present constraints. For example, you
+    may specify boundary constraint on P[0,0] and equality one:
+        0.1<=P[0,0]<=0.9
+        P[0,0]=0.5
+    Such  combination  of  constraints  will  be  silently  reduced  to  their
+    intersection, which is P[0,0]=0.5.
+
+    This  function  can  be  used  to  place bound   constraints  on arbitrary
+    subset  of  elements  of  P.  Set of constraints is specified by BndL/BndU
+    matrices, which may contain arbitrary combination  of  finite  numbers  or
+    infinities (like -INF<x<=0.5 or 0.1<=x<+INF).
+
+    You can also use MCPDAddBC() function which allows to ADD bound constraint
+    for one element of P without changing constraints for other elements.
+
+    These functions (MCPDSetBC and MCPDAddBC) interact as follows:
+    * there is internal matrix of bound constraints which is stored in the
+      MCPD solver
+    * MCPDSetBC() replaces this matrix by another one (SET)
+    * MCPDAddBC() modifies one element of this matrix and  leaves  other  ones
+      unchanged (ADD)
+    * thus  MCPDAddBC()  call  preserves  all  modifications  done by previous
+      calls,  while  MCPDSetBC()  completely discards all changes  done to the
+      equality constraints.
+
+    INPUT PARAMETERS:
+        S       -   solver
+        BndL    -   lower bounds constraints, array[N,N]. Elements of BndL can
+                    be finite numbers or -INF.
+        BndU    -   upper bounds constraints, array[N,N]. Elements of BndU can
+                    be finite numbers or +INF.
+
+      -- ALGLIB --
+         Copyright 23.05.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mcpdsetbc(mcpdstate s, double[,] bndl, double[,] bndu)
+    {
+
+        mcpd.mcpdsetbc(s.innerobj, bndl, bndu);
+        return;
+    }
+
+    /*************************************************************************
+    This function is used to add bound constraints  on  the  elements  of  the
+    transition matrix P.
+
+    MCPD solver has four types of constraints which can be placed on P:
+    * user-specified equality constraints (optional)
+    * user-specified bound constraints (optional)
+    * user-specified general linear constraints (optional)
+    * basic constraints (always present):
+      * non-negativity: P[i,j]>=0
+      * consistency: every column of P sums to 1.0
+
+    Final  constraints  which  are  passed  to  the  underlying  optimizer are
+    calculated  as  intersection  of all present constraints. For example, you
+    may specify boundary constraint on P[0,0] and equality one:
+        0.1<=P[0,0]<=0.9
+        P[0,0]=0.5
+    Such  combination  of  constraints  will  be  silently  reduced  to  their
+    intersection, which is P[0,0]=0.5.
+
+    This  function  can  be  used to ADD bound constraint for one element of P
+    without changing constraints for other elements.
+
+    You  can  also  use  MCPDSetBC()  function  which  allows to  place  bound
+    constraints  on arbitrary subset of elements of P.   Set of constraints is
+    specified  by  BndL/BndU matrices, which may contain arbitrary combination
+    of finite numbers or infinities (like -INF<x<=0.5 or 0.1<=x<+INF).
+
+    These functions (MCPDSetBC and MCPDAddBC) interact as follows:
+    * there is internal matrix of bound constraints which is stored in the
+      MCPD solver
+    * MCPDSetBC() replaces this matrix by another one (SET)
+    * MCPDAddBC() modifies one element of this matrix and  leaves  other  ones
+      unchanged (ADD)
+    * thus  MCPDAddBC()  call  preserves  all  modifications  done by previous
+      calls,  while  MCPDSetBC()  completely discards all changes  done to the
+      equality constraints.
+
+    INPUT PARAMETERS:
+        S       -   solver
+        I       -   row index of element being constrained
+        J       -   column index of element being constrained
+        BndL    -   lower bound
+        BndU    -   upper bound
+
+      -- ALGLIB --
+         Copyright 23.05.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mcpdaddbc(mcpdstate s, int i, int j, double bndl, double bndu)
+    {
+
+        mcpd.mcpdaddbc(s.innerobj, i, j, bndl, bndu);
+        return;
+    }
+
+    /*************************************************************************
+    This function is used to set linear equality/inequality constraints on the
+    elements of the transition matrix P.
+
+    This function can be used to set one or several general linear constraints
+    on the elements of P. Two types of constraints are supported:
+    * equality constraints
+    * inequality constraints (both less-or-equal and greater-or-equal)
+
+    Coefficients  of  constraints  are  specified  by  matrix  C (one  of  the
+    parameters).  One  row  of  C  corresponds  to  one  constraint.   Because
+    transition  matrix P has N*N elements,  we  need  N*N columns to store all
+    coefficients  (they  are  stored row by row), and one more column to store
+    right part - hence C has N*N+1 columns.  Constraint  kind is stored in the
+    CT array.
+
+    Thus, I-th linear constraint is
+        P[0,0]*C[I,0] + P[0,1]*C[I,1] + .. + P[0,N-1]*C[I,N-1] +
+            + P[1,0]*C[I,N] + P[1,1]*C[I,N+1] + ... +
+            + P[N-1,N-1]*C[I,N*N-1]  ?=?  C[I,N*N]
+    where ?=? can be either "=" (CT[i]=0), "<=" (CT[i]<0) or ">=" (CT[i]>0).
+
+    Your constraint may involve only some subset of P (less than N*N elements).
+    For example it can be something like
+        P[0,0] + P[0,1] = 0.5
+    In this case you still should pass matrix  with N*N+1 columns, but all its
+    elements (except for C[0,0], C[0,1] and C[0,N*N-1]) will be zero.
+
+    INPUT PARAMETERS:
+        S       -   solver
+        C       -   array[K,N*N+1] - coefficients of constraints
+                    (see above for complete description)
+        CT      -   array[K] - constraint types
+                    (see above for complete description)
+        K       -   number of equality/inequality constraints, K>=0:
+                    * if given, only leading K elements of C/CT are used
+                    * if not given, automatically determined from sizes of C/CT
+
+      -- ALGLIB --
+         Copyright 23.05.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mcpdsetlc(mcpdstate s, double[,] c, int[] ct, int k)
+    {
+
+        mcpd.mcpdsetlc(s.innerobj, c, ct, k);
+        return;
+    }
+    public static void mcpdsetlc(mcpdstate s, double[,] c, int[] ct)
+    {
+        int k;
+        if( (ap.rows(c)!=ap.len(ct)))
+            throw new alglibexception("Error while calling 'mcpdsetlc': looks like one of arguments has wrong size");
+
+        k = ap.rows(c);
+        mcpd.mcpdsetlc(s.innerobj, c, ct, k);
+
+        return;
+    }
+
+    /*************************************************************************
+    This function allows to  tune  amount  of  Tikhonov  regularization  being
+    applied to your problem.
+
+    By default, regularizing term is equal to r*||P-prior_P||^2, where r is  a
+    small non-zero value,  P is transition matrix, prior_P is identity matrix,
+    ||X||^2 is a sum of squared elements of X.
+
+    This  function  allows  you to change coefficient r. You can  also  change
+    prior values with MCPDSetPrior() function.
+
+    INPUT PARAMETERS:
+        S       -   solver
+        V       -   regularization  coefficient, finite non-negative value. It
+                    is  not  recommended  to specify zero value unless you are
+                    pretty sure that you want it.
+
+      -- ALGLIB --
+         Copyright 23.05.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mcpdsettikhonovregularizer(mcpdstate s, double v)
+    {
+
+        mcpd.mcpdsettikhonovregularizer(s.innerobj, v);
+        return;
+    }
+
+    /*************************************************************************
+    This  function  allows to set prior values used for regularization of your
+    problem.
+
+    By default, regularizing term is equal to r*||P-prior_P||^2, where r is  a
+    small non-zero value,  P is transition matrix, prior_P is identity matrix,
+    ||X||^2 is a sum of squared elements of X.
+
+    This  function  allows  you to change prior values prior_P. You  can  also
+    change r with MCPDSetTikhonovRegularizer() function.
+
+    INPUT PARAMETERS:
+        S       -   solver
+        PP      -   array[N,N], matrix of prior values:
+                    1. elements must be real numbers from [0,1]
+                    2. columns must sum to 1.0.
+                    First property is checked (exception is thrown otherwise),
+                    while second one is not checked/enforced.
+
+      -- ALGLIB --
+         Copyright 23.05.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mcpdsetprior(mcpdstate s, double[,] pp)
+    {
+
+        mcpd.mcpdsetprior(s.innerobj, pp);
+        return;
+    }
+
+    /*************************************************************************
+    This function is used to change prediction weights
+
+    MCPD solver scales prediction errors as follows
+        Error(P) = ||W*(y-P*x)||^2
+    where
+        x is a system state at time t
+        y is a system state at time t+1
+        P is a transition matrix
+        W is a diagonal scaling matrix
+
+    By default, weights are chosen in order  to  minimize  relative prediction
+    error instead of absolute one. For example, if one component of  state  is
+    about 0.5 in magnitude and another one is about 0.05, then algorithm  will
+    make corresponding weights equal to 2.0 and 20.0.
+
+    INPUT PARAMETERS:
+        S       -   solver
+        PW      -   array[N], weights:
+                    * must be non-negative values (exception will be thrown otherwise)
+                    * zero values will be replaced by automatically chosen values
+
+      -- ALGLIB --
+         Copyright 23.05.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mcpdsetpredictionweights(mcpdstate s, double[] pw)
+    {
+
+        mcpd.mcpdsetpredictionweights(s.innerobj, pw);
+        return;
+    }
+
+    /*************************************************************************
+    This function is used to start solution of the MCPD problem.
+
+    After return from this function, you can use MCPDResults() to get solution
+    and completion code.
+
+      -- ALGLIB --
+         Copyright 23.05.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mcpdsolve(mcpdstate s)
+    {
+
+        mcpd.mcpdsolve(s.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    MCPD results
+
+    INPUT PARAMETERS:
+        State   -   algorithm state
+
+    OUTPUT PARAMETERS:
+        P       -   array[N,N], transition matrix
+        Rep     -   optimization report. You should check Rep.TerminationType
+                    in  order  to  distinguish  successful  termination  from
+                    unsuccessful one. Speaking short, positive values  denote
+                    success, negative ones are failures.
+                    More information about fields of this  structure  can  be
+                    found in the comments on MCPDReport datatype.
+
+
+      -- ALGLIB --
+         Copyright 23.05.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mcpdresults(mcpdstate s, out double[,] p, out mcpdreport rep)
+    {
+        p = new double[0,0];
+        rep = new mcpdreport();
+        mcpd.mcpdresults(s.innerobj, ref p, rep.innerobj);
+        return;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    Training report:
+        * NGrad     - number of gradient calculations
+        * NHess     - number of Hessian calculations
+        * NCholesky - number of Cholesky decompositions
+    *************************************************************************/
+    public class mlpreport
+    {
+        //
+        // Public declarations
+        //
+        public int ngrad { get { return _innerobj.ngrad; } set { _innerobj.ngrad = value; } }
+        public int nhess { get { return _innerobj.nhess; } set { _innerobj.nhess = value; } }
+        public int ncholesky { get { return _innerobj.ncholesky; } set { _innerobj.ncholesky = value; } }
+
+        public mlpreport()
+        {
+            _innerobj = new mlptrain.mlpreport();
+        }
+
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private mlptrain.mlpreport _innerobj;
+        public mlptrain.mlpreport innerobj { get { return _innerobj; } }
+        public mlpreport(mlptrain.mlpreport obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+
+    /*************************************************************************
+    Cross-validation estimates of generalization error
+    *************************************************************************/
+    public class mlpcvreport
+    {
+        //
+        // Public declarations
+        //
+        public double relclserror { get { return _innerobj.relclserror; } set { _innerobj.relclserror = value; } }
+        public double avgce { get { return _innerobj.avgce; } set { _innerobj.avgce = value; } }
+        public double rmserror { get { return _innerobj.rmserror; } set { _innerobj.rmserror = value; } }
+        public double avgerror { get { return _innerobj.avgerror; } set { _innerobj.avgerror = value; } }
+        public double avgrelerror { get { return _innerobj.avgrelerror; } set { _innerobj.avgrelerror = value; } }
+
+        public mlpcvreport()
+        {
+            _innerobj = new mlptrain.mlpcvreport();
+        }
+
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private mlptrain.mlpcvreport _innerobj;
+        public mlptrain.mlpcvreport innerobj { get { return _innerobj; } }
+        public mlpcvreport(mlptrain.mlpcvreport obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+    /*************************************************************************
+    Neural network training  using  modified  Levenberg-Marquardt  with  exact
+    Hessian calculation and regularization. Subroutine trains  neural  network
+    with restarts from random positions. Algorithm is well  suited  for  small
+    and medium scale problems (hundreds of weights).
+
+    INPUT PARAMETERS:
+        Network     -   neural network with initialized geometry
+        XY          -   training set
+        NPoints     -   training set size
+        Decay       -   weight decay constant, >=0.001
+                        Decay term 'Decay*||Weights||^2' is added to error
+                        function.
+                        If you don't know what Decay to choose, use 0.001.
+        Restarts    -   number of restarts from random position, >0.
+                        If you don't know what Restarts to choose, use 2.
+
+    OUTPUT PARAMETERS:
+        Network     -   trained neural network.
+        Info        -   return code:
+                        * -9, if internal matrix inverse subroutine failed
+                        * -2, if there is a point with class number
+                              outside of [0..NOut-1].
+                        * -1, if wrong parameters specified
+                              (NPoints<0, Restarts<1).
+                        *  2, if task has been solved.
+        Rep         -   training report
+
+      -- ALGLIB --
+         Copyright 10.03.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlptrainlm(multilayerperceptron network, double[,] xy, int npoints, double decay, int restarts, out int info, out mlpreport rep)
+    {
+        info = 0;
+        rep = new mlpreport();
+        mlptrain.mlptrainlm(network.innerobj, xy, npoints, decay, restarts, ref info, rep.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    Neural  network  training  using  L-BFGS  algorithm  with  regularization.
+    Subroutine  trains  neural  network  with  restarts from random positions.
+    Algorithm  is  well  suited  for  problems  of  any dimensionality (memory
+    requirements and step complexity are linear by weights number).
+
+    INPUT PARAMETERS:
+        Network     -   neural network with initialized geometry
+        XY          -   training set
+        NPoints     -   training set size
+        Decay       -   weight decay constant, >=0.001
+                        Decay term 'Decay*||Weights||^2' is added to error
+                        function.
+                        If you don't know what Decay to choose, use 0.001.
+        Restarts    -   number of restarts from random position, >0.
+                        If you don't know what Restarts to choose, use 2.
+        WStep       -   stopping criterion. Algorithm stops if  step  size  is
+                        less than WStep. Recommended value - 0.01.  Zero  step
+                        size means stopping after MaxIts iterations.
+        MaxIts      -   stopping   criterion.  Algorithm  stops  after  MaxIts
+                        iterations (NOT gradient  calculations).  Zero  MaxIts
+                        means stopping when step is sufficiently small.
+
+    OUTPUT PARAMETERS:
+        Network     -   trained neural network.
+        Info        -   return code:
+                        * -8, if both WStep=0 and MaxIts=0
+                        * -2, if there is a point with class number
+                              outside of [0..NOut-1].
+                        * -1, if wrong parameters specified
+                              (NPoints<0, Restarts<1).
+                        *  2, if task has been solved.
+        Rep         -   training report
+
+      -- ALGLIB --
+         Copyright 09.12.2007 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlptrainlbfgs(multilayerperceptron network, double[,] xy, int npoints, double decay, int restarts, double wstep, int maxits, out int info, out mlpreport rep)
+    {
+        info = 0;
+        rep = new mlpreport();
+        mlptrain.mlptrainlbfgs(network.innerobj, xy, npoints, decay, restarts, wstep, maxits, ref info, rep.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    Neural network training using early stopping (base algorithm - L-BFGS with
+    regularization).
+
+    INPUT PARAMETERS:
+        Network     -   neural network with initialized geometry
+        TrnXY       -   training set
+        TrnSize     -   training set size, TrnSize>0
+        ValXY       -   validation set
+        ValSize     -   validation set size, ValSize>0
+        Decay       -   weight decay constant, >=0.001
+                        Decay term 'Decay*||Weights||^2' is added to error
+                        function.
+                        If you don't know what Decay to choose, use 0.001.
+        Restarts    -   number of restarts, either:
+                        * strictly positive number - algorithm make specified
+                          number of restarts from random position.
+                        * -1, in which case algorithm makes exactly one run
+                          from the initial state of the network (no randomization).
+                        If you don't know what Restarts to choose, choose one
+                        one the following:
+                        * -1 (deterministic start)
+                        * +1 (one random restart)
+                        * +5 (moderate amount of random restarts)
+
+    OUTPUT PARAMETERS:
+        Network     -   trained neural network.
+        Info        -   return code:
+                        * -2, if there is a point with class number
+                              outside of [0..NOut-1].
+                        * -1, if wrong parameters specified
+                              (NPoints<0, Restarts<1, ...).
+                        *  2, task has been solved, stopping  criterion  met -
+                              sufficiently small step size.  Not expected  (we
+                              use  EARLY  stopping)  but  possible  and not an
+                              error.
+                        *  6, task has been solved, stopping  criterion  met -
+                              increasing of validation set error.
+        Rep         -   training report
+
+    NOTE:
+
+    Algorithm stops if validation set error increases for  a  long  enough  or
+    step size is small enought  (there  are  task  where  validation  set  may
+    decrease for eternity). In any case solution returned corresponds  to  the
+    minimum of validation set error.
+
+      -- ALGLIB --
+         Copyright 10.03.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlptraines(multilayerperceptron network, double[,] trnxy, int trnsize, double[,] valxy, int valsize, double decay, int restarts, out int info, out mlpreport rep)
+    {
+        info = 0;
+        rep = new mlpreport();
+        mlptrain.mlptraines(network.innerobj, trnxy, trnsize, valxy, valsize, decay, restarts, ref info, rep.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    Cross-validation estimate of generalization error.
+
+    Base algorithm - L-BFGS.
+
+    INPUT PARAMETERS:
+        Network     -   neural network with initialized geometry.   Network is
+                        not changed during cross-validation -  it is used only
+                        as a representative of its architecture.
+        XY          -   training set.
+        SSize       -   training set size
+        Decay       -   weight  decay, same as in MLPTrainLBFGS
+        Restarts    -   number of restarts, >0.
+                        restarts are counted for each partition separately, so
+                        total number of restarts will be Restarts*FoldsCount.
+        WStep       -   stopping criterion, same as in MLPTrainLBFGS
+        MaxIts      -   stopping criterion, same as in MLPTrainLBFGS
+        FoldsCount  -   number of folds in k-fold cross-validation,
+                        2<=FoldsCount<=SSize.
+                        recommended value: 10.
+
+    OUTPUT PARAMETERS:
+        Info        -   return code, same as in MLPTrainLBFGS
+        Rep         -   report, same as in MLPTrainLM/MLPTrainLBFGS
+        CVRep       -   generalization error estimates
+
+      -- ALGLIB --
+         Copyright 09.12.2007 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlpkfoldcvlbfgs(multilayerperceptron network, double[,] xy, int npoints, double decay, int restarts, double wstep, int maxits, int foldscount, out int info, out mlpreport rep, out mlpcvreport cvrep)
+    {
+        info = 0;
+        rep = new mlpreport();
+        cvrep = new mlpcvreport();
+        mlptrain.mlpkfoldcvlbfgs(network.innerobj, xy, npoints, decay, restarts, wstep, maxits, foldscount, ref info, rep.innerobj, cvrep.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    Cross-validation estimate of generalization error.
+
+    Base algorithm - Levenberg-Marquardt.
+
+    INPUT PARAMETERS:
+        Network     -   neural network with initialized geometry.   Network is
+                        not changed during cross-validation -  it is used only
+                        as a representative of its architecture.
+        XY          -   training set.
+        SSize       -   training set size
+        Decay       -   weight  decay, same as in MLPTrainLBFGS
+        Restarts    -   number of restarts, >0.
+                        restarts are counted for each partition separately, so
+                        total number of restarts will be Restarts*FoldsCount.
+        FoldsCount  -   number of folds in k-fold cross-validation,
+                        2<=FoldsCount<=SSize.
+                        recommended value: 10.
+
+    OUTPUT PARAMETERS:
+        Info        -   return code, same as in MLPTrainLBFGS
+        Rep         -   report, same as in MLPTrainLM/MLPTrainLBFGS
+        CVRep       -   generalization error estimates
+
+      -- ALGLIB --
+         Copyright 09.12.2007 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlpkfoldcvlm(multilayerperceptron network, double[,] xy, int npoints, double decay, int restarts, int foldscount, out int info, out mlpreport rep, out mlpcvreport cvrep)
+    {
+        info = 0;
+        rep = new mlpreport();
+        cvrep = new mlpcvreport();
+        mlptrain.mlpkfoldcvlm(network.innerobj, xy, npoints, decay, restarts, foldscount, ref info, rep.innerobj, cvrep.innerobj);
+        return;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    Neural networks ensemble
+    *************************************************************************/
+    public class mlpensemble
+    {
+        //
+        // Public declarations
+        //
+
+        public mlpensemble()
+        {
+            _innerobj = new mlpe.mlpensemble();
+        }
+
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private mlpe.mlpensemble _innerobj;
+        public mlpe.mlpensemble innerobj { get { return _innerobj; } }
+        public mlpensemble(mlpe.mlpensemble obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+
+    /*************************************************************************
+    This function serializes data structure to string.
+
+    Important properties of s_out:
+    * it contains alphanumeric characters, dots, underscores, minus signs
+    * these symbols are grouped into words, which are separated by spaces
+      and Windows-style (CR+LF) newlines
+    * although  serializer  uses  spaces and CR+LF as separators, you can 
+      replace any separator character by arbitrary combination of spaces,
+      tabs, Windows or Unix newlines. It allows flexible reformatting  of
+      the  string  in  case you want to include it into text or XML file. 
+      But you should not insert separators into the middle of the "words"
+      nor you should change case of letters.
+    * s_out can be freely moved between 32-bit and 64-bit systems, little
+      and big endian machines, and so on. You can serialize structure  on
+      32-bit machine and unserialize it on 64-bit one (or vice versa), or
+      serialize  it  on  SPARC  and  unserialize  on  x86.  You  can also 
+      serialize  it  in  C# version of ALGLIB and unserialize in C++ one, 
+      and vice versa.
+    *************************************************************************/
+    public static void mlpeserialize(mlpensemble obj, out string s_out)
+    {
+        alglib.serializer s = new alglib.serializer();
+        s.alloc_start();
+        mlpe.mlpealloc(s, obj.innerobj);
+        s.sstart_str();
+        mlpe.mlpeserialize(s, obj.innerobj);
+        s.stop();
+        s_out = s.get_string();
+    }
+
+
+    /*************************************************************************
+    This function unserializes data structure from string.
+    *************************************************************************/
+    public static void mlpeunserialize(string s_in, out mlpensemble obj)
+    {
+        alglib.serializer s = new alglib.serializer();
+        obj = new mlpensemble();
+        s.ustart_str(s_in);
+        mlpe.mlpeunserialize(s, obj.innerobj);
+        s.stop();
+    }
+
+    /*************************************************************************
+    Like MLPCreate0, but for ensembles.
+
+      -- ALGLIB --
+         Copyright 18.02.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlpecreate0(int nin, int nout, int ensemblesize, out mlpensemble ensemble)
+    {
+        ensemble = new mlpensemble();
+        mlpe.mlpecreate0(nin, nout, ensemblesize, ensemble.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    Like MLPCreate1, but for ensembles.
+
+      -- ALGLIB --
+         Copyright 18.02.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlpecreate1(int nin, int nhid, int nout, int ensemblesize, out mlpensemble ensemble)
+    {
+        ensemble = new mlpensemble();
+        mlpe.mlpecreate1(nin, nhid, nout, ensemblesize, ensemble.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    Like MLPCreate2, but for ensembles.
+
+      -- ALGLIB --
+         Copyright 18.02.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlpecreate2(int nin, int nhid1, int nhid2, int nout, int ensemblesize, out mlpensemble ensemble)
+    {
+        ensemble = new mlpensemble();
+        mlpe.mlpecreate2(nin, nhid1, nhid2, nout, ensemblesize, ensemble.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    Like MLPCreateB0, but for ensembles.
+
+      -- ALGLIB --
+         Copyright 18.02.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlpecreateb0(int nin, int nout, double b, double d, int ensemblesize, out mlpensemble ensemble)
+    {
+        ensemble = new mlpensemble();
+        mlpe.mlpecreateb0(nin, nout, b, d, ensemblesize, ensemble.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    Like MLPCreateB1, but for ensembles.
+
+      -- ALGLIB --
+         Copyright 18.02.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlpecreateb1(int nin, int nhid, int nout, double b, double d, int ensemblesize, out mlpensemble ensemble)
+    {
+        ensemble = new mlpensemble();
+        mlpe.mlpecreateb1(nin, nhid, nout, b, d, ensemblesize, ensemble.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    Like MLPCreateB2, but for ensembles.
+
+      -- ALGLIB --
+         Copyright 18.02.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlpecreateb2(int nin, int nhid1, int nhid2, int nout, double b, double d, int ensemblesize, out mlpensemble ensemble)
+    {
+        ensemble = new mlpensemble();
+        mlpe.mlpecreateb2(nin, nhid1, nhid2, nout, b, d, ensemblesize, ensemble.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    Like MLPCreateR0, but for ensembles.
+
+      -- ALGLIB --
+         Copyright 18.02.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlpecreater0(int nin, int nout, double a, double b, int ensemblesize, out mlpensemble ensemble)
+    {
+        ensemble = new mlpensemble();
+        mlpe.mlpecreater0(nin, nout, a, b, ensemblesize, ensemble.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    Like MLPCreateR1, but for ensembles.
+
+      -- ALGLIB --
+         Copyright 18.02.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlpecreater1(int nin, int nhid, int nout, double a, double b, int ensemblesize, out mlpensemble ensemble)
+    {
+        ensemble = new mlpensemble();
+        mlpe.mlpecreater1(nin, nhid, nout, a, b, ensemblesize, ensemble.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    Like MLPCreateR2, but for ensembles.
+
+      -- ALGLIB --
+         Copyright 18.02.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlpecreater2(int nin, int nhid1, int nhid2, int nout, double a, double b, int ensemblesize, out mlpensemble ensemble)
+    {
+        ensemble = new mlpensemble();
+        mlpe.mlpecreater2(nin, nhid1, nhid2, nout, a, b, ensemblesize, ensemble.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    Like MLPCreateC0, but for ensembles.
+
+      -- ALGLIB --
+         Copyright 18.02.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlpecreatec0(int nin, int nout, int ensemblesize, out mlpensemble ensemble)
+    {
+        ensemble = new mlpensemble();
+        mlpe.mlpecreatec0(nin, nout, ensemblesize, ensemble.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    Like MLPCreateC1, but for ensembles.
+
+      -- ALGLIB --
+         Copyright 18.02.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlpecreatec1(int nin, int nhid, int nout, int ensemblesize, out mlpensemble ensemble)
+    {
+        ensemble = new mlpensemble();
+        mlpe.mlpecreatec1(nin, nhid, nout, ensemblesize, ensemble.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    Like MLPCreateC2, but for ensembles.
+
+      -- ALGLIB --
+         Copyright 18.02.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlpecreatec2(int nin, int nhid1, int nhid2, int nout, int ensemblesize, out mlpensemble ensemble)
+    {
+        ensemble = new mlpensemble();
+        mlpe.mlpecreatec2(nin, nhid1, nhid2, nout, ensemblesize, ensemble.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    Creates ensemble from network. Only network geometry is copied.
+
+      -- ALGLIB --
+         Copyright 17.02.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlpecreatefromnetwork(multilayerperceptron network, int ensemblesize, out mlpensemble ensemble)
+    {
+        ensemble = new mlpensemble();
+        mlpe.mlpecreatefromnetwork(network.innerobj, ensemblesize, ensemble.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    Randomization of MLP ensemble
+
+      -- ALGLIB --
+         Copyright 17.02.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlperandomize(mlpensemble ensemble)
+    {
+
+        mlpe.mlperandomize(ensemble.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    Return ensemble properties (number of inputs and outputs).
+
+      -- ALGLIB --
+         Copyright 17.02.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlpeproperties(mlpensemble ensemble, out int nin, out int nout)
+    {
+        nin = 0;
+        nout = 0;
+        mlpe.mlpeproperties(ensemble.innerobj, ref nin, ref nout);
+        return;
+    }
+
+    /*************************************************************************
+    Return normalization type (whether ensemble is SOFTMAX-normalized or not).
+
+      -- ALGLIB --
+         Copyright 17.02.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static bool mlpeissoftmax(mlpensemble ensemble)
+    {
+
+        bool result = mlpe.mlpeissoftmax(ensemble.innerobj);
+        return result;
+    }
+
+    /*************************************************************************
+    Procesing
+
+    INPUT PARAMETERS:
+        Ensemble-   neural networks ensemble
+        X       -   input vector,  array[0..NIn-1].
+        Y       -   (possibly) preallocated buffer; if size of Y is less than
+                    NOut, it will be reallocated. If it is large enough, it
+                    is NOT reallocated, so we can save some time on reallocation.
+
+
+    OUTPUT PARAMETERS:
+        Y       -   result. Regression estimate when solving regression  task,
+                    vector of posterior probabilities for classification task.
+
+      -- ALGLIB --
+         Copyright 17.02.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlpeprocess(mlpensemble ensemble, double[] x, ref double[] y)
+    {
+
+        mlpe.mlpeprocess(ensemble.innerobj, x, ref y);
+        return;
+    }
+
+    /*************************************************************************
+    'interactive'  variant  of  MLPEProcess  for  languages  like Python which
+    support constructs like "Y = MLPEProcess(LM,X)" and interactive mode of the
+    interpreter
+
+    This function allocates new array on each call,  so  it  is  significantly
+    slower than its 'non-interactive' counterpart, but it is  more  convenient
+    when you call it from command line.
+
+      -- ALGLIB --
+         Copyright 17.02.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlpeprocessi(mlpensemble ensemble, double[] x, out double[] y)
+    {
+        y = new double[0];
+        mlpe.mlpeprocessi(ensemble.innerobj, x, ref y);
+        return;
+    }
+
+    /*************************************************************************
+    Relative classification error on the test set
+
+    INPUT PARAMETERS:
+        Ensemble-   ensemble
+        XY      -   test set
+        NPoints -   test set size
+
+    RESULT:
+        percent of incorrectly classified cases.
+        Works both for classifier betwork and for regression networks which
+    are used as classifiers.
+
+      -- ALGLIB --
+         Copyright 17.02.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static double mlperelclserror(mlpensemble ensemble, double[,] xy, int npoints)
+    {
+
+        double result = mlpe.mlperelclserror(ensemble.innerobj, xy, npoints);
+        return result;
+    }
+
+    /*************************************************************************
+    Average cross-entropy (in bits per element) on the test set
+
+    INPUT PARAMETERS:
+        Ensemble-   ensemble
+        XY      -   test set
+        NPoints -   test set size
+
+    RESULT:
+        CrossEntropy/(NPoints*LN(2)).
+        Zero if ensemble solves regression task.
+
+      -- ALGLIB --
+         Copyright 17.02.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static double mlpeavgce(mlpensemble ensemble, double[,] xy, int npoints)
+    {
+
+        double result = mlpe.mlpeavgce(ensemble.innerobj, xy, npoints);
+        return result;
+    }
+
+    /*************************************************************************
+    RMS error on the test set
+
+    INPUT PARAMETERS:
+        Ensemble-   ensemble
+        XY      -   test set
+        NPoints -   test set size
+
+    RESULT:
+        root mean square error.
+        Its meaning for regression task is obvious. As for classification task
+    RMS error means error when estimating posterior probabilities.
+
+      -- ALGLIB --
+         Copyright 17.02.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static double mlpermserror(mlpensemble ensemble, double[,] xy, int npoints)
+    {
+
+        double result = mlpe.mlpermserror(ensemble.innerobj, xy, npoints);
+        return result;
+    }
+
+    /*************************************************************************
+    Average error on the test set
+
+    INPUT PARAMETERS:
+        Ensemble-   ensemble
+        XY      -   test set
+        NPoints -   test set size
+
+    RESULT:
+        Its meaning for regression task is obvious. As for classification task
+    it means average error when estimating posterior probabilities.
+
+      -- ALGLIB --
+         Copyright 17.02.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static double mlpeavgerror(mlpensemble ensemble, double[,] xy, int npoints)
+    {
+
+        double result = mlpe.mlpeavgerror(ensemble.innerobj, xy, npoints);
+        return result;
+    }
+
+    /*************************************************************************
+    Average relative error on the test set
+
+    INPUT PARAMETERS:
+        Ensemble-   ensemble
+        XY      -   test set
+        NPoints -   test set size
+
+    RESULT:
+        Its meaning for regression task is obvious. As for classification task
+    it means average relative error when estimating posterior probabilities.
+
+      -- ALGLIB --
+         Copyright 17.02.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static double mlpeavgrelerror(mlpensemble ensemble, double[,] xy, int npoints)
+    {
+
+        double result = mlpe.mlpeavgrelerror(ensemble.innerobj, xy, npoints);
+        return result;
+    }
+
+    /*************************************************************************
+    Training neural networks ensemble using  bootstrap  aggregating (bagging).
+    Modified Levenberg-Marquardt algorithm is used as base training method.
+
+    INPUT PARAMETERS:
+        Ensemble    -   model with initialized geometry
+        XY          -   training set
+        NPoints     -   training set size
+        Decay       -   weight decay coefficient, >=0.001
+        Restarts    -   restarts, >0.
+
+    OUTPUT PARAMETERS:
+        Ensemble    -   trained model
+        Info        -   return code:
+                        * -2, if there is a point with class number
+                              outside of [0..NClasses-1].
+                        * -1, if incorrect parameters was passed
+                              (NPoints<0, Restarts<1).
+                        *  2, if task has been solved.
+        Rep         -   training report.
+        OOBErrors   -   out-of-bag generalization error estimate
+
+      -- ALGLIB --
+         Copyright 17.02.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlpebagginglm(mlpensemble ensemble, double[,] xy, int npoints, double decay, int restarts, out int info, out mlpreport rep, out mlpcvreport ooberrors)
+    {
+        info = 0;
+        rep = new mlpreport();
+        ooberrors = new mlpcvreport();
+        mlpe.mlpebagginglm(ensemble.innerobj, xy, npoints, decay, restarts, ref info, rep.innerobj, ooberrors.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    Training neural networks ensemble using  bootstrap  aggregating (bagging).
+    L-BFGS algorithm is used as base training method.
+
+    INPUT PARAMETERS:
+        Ensemble    -   model with initialized geometry
+        XY          -   training set
+        NPoints     -   training set size
+        Decay       -   weight decay coefficient, >=0.001
+        Restarts    -   restarts, >0.
+        WStep       -   stopping criterion, same as in MLPTrainLBFGS
+        MaxIts      -   stopping criterion, same as in MLPTrainLBFGS
+
+    OUTPUT PARAMETERS:
+        Ensemble    -   trained model
+        Info        -   return code:
+                        * -8, if both WStep=0 and MaxIts=0
+                        * -2, if there is a point with class number
+                              outside of [0..NClasses-1].
+                        * -1, if incorrect parameters was passed
+                              (NPoints<0, Restarts<1).
+                        *  2, if task has been solved.
+        Rep         -   training report.
+        OOBErrors   -   out-of-bag generalization error estimate
+
+      -- ALGLIB --
+         Copyright 17.02.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlpebagginglbfgs(mlpensemble ensemble, double[,] xy, int npoints, double decay, int restarts, double wstep, int maxits, out int info, out mlpreport rep, out mlpcvreport ooberrors)
+    {
+        info = 0;
+        rep = new mlpreport();
+        ooberrors = new mlpcvreport();
+        mlpe.mlpebagginglbfgs(ensemble.innerobj, xy, npoints, decay, restarts, wstep, maxits, ref info, rep.innerobj, ooberrors.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    Training neural networks ensemble using early stopping.
+
+    INPUT PARAMETERS:
+        Ensemble    -   model with initialized geometry
+        XY          -   training set
+        NPoints     -   training set size
+        Decay       -   weight decay coefficient, >=0.001
+        Restarts    -   restarts, >0.
+
+    OUTPUT PARAMETERS:
+        Ensemble    -   trained model
+        Info        -   return code:
+                        * -2, if there is a point with class number
+                              outside of [0..NClasses-1].
+                        * -1, if incorrect parameters was passed
+                              (NPoints<0, Restarts<1).
+                        *  6, if task has been solved.
+        Rep         -   training report.
+        OOBErrors   -   out-of-bag generalization error estimate
+
+      -- ALGLIB --
+         Copyright 10.03.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlpetraines(mlpensemble ensemble, double[,] xy, int npoints, double decay, int restarts, out int info, out mlpreport rep)
+    {
+        info = 0;
+        rep = new mlpreport();
+        mlpe.mlpetraines(ensemble.innerobj, xy, npoints, decay, restarts, ref info, rep.innerobj);
+        return;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    Principal components analysis
+
+    Subroutine  builds  orthogonal  basis  where  first  axis  corresponds  to
+    direction with maximum variance, second axis maximizes variance in subspace
+    orthogonal to first axis and so on.
+
+    It should be noted that, unlike LDA, PCA does not use class labels.
+
+    INPUT PARAMETERS:
+        X           -   dataset, array[0..NPoints-1,0..NVars-1].
+                        matrix contains ONLY INDEPENDENT VARIABLES.
+        NPoints     -   dataset size, NPoints>=0
+        NVars       -   number of independent variables, NVars>=1
+
+    ÂÛÕÎÄÍÛÅ ÏÀÐÀÌÅÒÐÛ:
+        Info        -   return code:
+                        * -4, if SVD subroutine haven't converged
+                        * -1, if wrong parameters has been passed (NPoints<0,
+                              NVars<1)
+                        *  1, if task is solved
+        S2          -   array[0..NVars-1]. variance values corresponding
+                        to basis vectors.
+        V           -   array[0..NVars-1,0..NVars-1]
+                        matrix, whose columns store basis vectors.
+
+      -- ALGLIB --
+         Copyright 25.08.2008 by Bochkanov Sergey
+    *************************************************************************/
+    public static void pcabuildbasis(double[,] x, int npoints, int nvars, out int info, out double[] s2, out double[,] v)
+    {
+        info = 0;
+        s2 = new double[0];
+        v = new double[0,0];
+        pca.pcabuildbasis(x, npoints, nvars, ref info, ref s2, ref v);
+        return;
+    }
+
+}
+public partial class alglib
+{
+    public class bdss
+    {
+        public class cvreport
+        {
+            public double relclserror;
+            public double avgce;
+            public double rmserror;
+            public double avgerror;
+            public double avgrelerror;
+        };
+
+
+
+
+        /*************************************************************************
+        This set of routines (DSErrAllocate, DSErrAccumulate, DSErrFinish)
+        calculates different error functions (classification error, cross-entropy,
+        rms, avg, avg.rel errors).
+
+        1. DSErrAllocate prepares buffer.
+        2. DSErrAccumulate accumulates individual errors:
+            * Y contains predicted output (posterior probabilities for classification)
+            * DesiredY contains desired output (class number for classification)
+        3. DSErrFinish outputs results:
+           * Buf[0] contains relative classification error (zero for regression tasks)
+           * Buf[1] contains avg. cross-entropy (zero for regression tasks)
+           * Buf[2] contains rms error (regression, classification)
+           * Buf[3] contains average error (regression, classification)
+           * Buf[4] contains average relative error (regression, classification)
+           
+        NOTES(1):
+            "NClasses>0" means that we have classification task.
+            "NClasses<0" means regression task with -NClasses real outputs.
+
+        NOTES(2):
+            rms. avg, avg.rel errors for classification tasks are interpreted as
+            errors in posterior probabilities with respect to probabilities given
+            by training/test set.
+
+          -- ALGLIB --
+             Copyright 11.01.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void dserrallocate(int nclasses,
+            ref double[] buf)
+        {
+            buf = new double[0];
+
+            buf = new double[7+1];
+            buf[0] = 0;
+            buf[1] = 0;
+            buf[2] = 0;
+            buf[3] = 0;
+            buf[4] = 0;
+            buf[5] = nclasses;
+            buf[6] = 0;
+            buf[7] = 0;
+        }
+
+
+        /*************************************************************************
+        See DSErrAllocate for comments on this routine.
+
+          -- ALGLIB --
+             Copyright 11.01.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void dserraccumulate(ref double[] buf,
+            double[] y,
+            double[] desiredy)
+        {
+            int nclasses = 0;
+            int nout = 0;
+            int offs = 0;
+            int mmax = 0;
+            int rmax = 0;
+            int j = 0;
+            double v = 0;
+            double ev = 0;
+
+            offs = 5;
+            nclasses = (int)Math.Round(buf[offs]);
+            if( nclasses>0 )
+            {
+                
+                //
+                // Classification
+                //
+                rmax = (int)Math.Round(desiredy[0]);
+                mmax = 0;
+                for(j=1; j<=nclasses-1; j++)
+                {
+                    if( (double)(y[j])>(double)(y[mmax]) )
+                    {
+                        mmax = j;
+                    }
+                }
+                if( mmax!=rmax )
+                {
+                    buf[0] = buf[0]+1;
+                }
+                if( (double)(y[rmax])>(double)(0) )
+                {
+                    buf[1] = buf[1]-Math.Log(y[rmax]);
+                }
+                else
+                {
+                    buf[1] = buf[1]+Math.Log(math.maxrealnumber);
+                }
+                for(j=0; j<=nclasses-1; j++)
+                {
+                    v = y[j];
+                    if( j==rmax )
+                    {
+                        ev = 1;
+                    }
+                    else
+                    {
+                        ev = 0;
+                    }
+                    buf[2] = buf[2]+math.sqr(v-ev);
+                    buf[3] = buf[3]+Math.Abs(v-ev);
+                    if( (double)(ev)!=(double)(0) )
+                    {
+                        buf[4] = buf[4]+Math.Abs((v-ev)/ev);
+                        buf[offs+2] = buf[offs+2]+1;
+                    }
+                }
+                buf[offs+1] = buf[offs+1]+1;
+            }
+            else
+            {
+                
+                //
+                // Regression
+                //
+                nout = -nclasses;
+                rmax = 0;
+                for(j=1; j<=nout-1; j++)
+                {
+                    if( (double)(desiredy[j])>(double)(desiredy[rmax]) )
+                    {
+                        rmax = j;
+                    }
+                }
+                mmax = 0;
+                for(j=1; j<=nout-1; j++)
+                {
+                    if( (double)(y[j])>(double)(y[mmax]) )
+                    {
+                        mmax = j;
+                    }
+                }
+                if( mmax!=rmax )
+                {
+                    buf[0] = buf[0]+1;
+                }
+                for(j=0; j<=nout-1; j++)
+                {
+                    v = y[j];
+                    ev = desiredy[j];
+                    buf[2] = buf[2]+math.sqr(v-ev);
+                    buf[3] = buf[3]+Math.Abs(v-ev);
+                    if( (double)(ev)!=(double)(0) )
+                    {
+                        buf[4] = buf[4]+Math.Abs((v-ev)/ev);
+                        buf[offs+2] = buf[offs+2]+1;
+                    }
+                }
+                buf[offs+1] = buf[offs+1]+1;
+            }
+        }
+
+
+        /*************************************************************************
+        See DSErrAllocate for comments on this routine.
+
+          -- ALGLIB --
+             Copyright 11.01.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void dserrfinish(ref double[] buf)
+        {
+            int nout = 0;
+            int offs = 0;
+
+            offs = 5;
+            nout = Math.Abs((int)Math.Round(buf[offs]));
+            if( (double)(buf[offs+1])!=(double)(0) )
+            {
+                buf[0] = buf[0]/buf[offs+1];
+                buf[1] = buf[1]/buf[offs+1];
+                buf[2] = Math.Sqrt(buf[2]/(nout*buf[offs+1]));
+                buf[3] = buf[3]/(nout*buf[offs+1]);
+            }
+            if( (double)(buf[offs+2])!=(double)(0) )
+            {
+                buf[4] = buf[4]/buf[offs+2];
+            }
+        }
+
+
+        /*************************************************************************
+
+          -- ALGLIB --
+             Copyright 19.05.2008 by Bochkanov Sergey
+        *************************************************************************/
+        public static void dsnormalize(ref double[,] xy,
+            int npoints,
+            int nvars,
+            ref int info,
+            ref double[] means,
+            ref double[] sigmas)
+        {
+            int i = 0;
+            int j = 0;
+            double[] tmp = new double[0];
+            double mean = 0;
+            double variance = 0;
+            double skewness = 0;
+            double kurtosis = 0;
+            int i_ = 0;
+
+            info = 0;
+            means = new double[0];
+            sigmas = new double[0];
+
+            
+            //
+            // Test parameters
+            //
+            if( npoints<=0 || nvars<1 )
+            {
+                info = -1;
+                return;
+            }
+            info = 1;
+            
+            //
+            // Standartization
+            //
+            means = new double[nvars-1+1];
+            sigmas = new double[nvars-1+1];
+            tmp = new double[npoints-1+1];
+            for(j=0; j<=nvars-1; j++)
+            {
+                for(i_=0; i_<=npoints-1;i_++)
+                {
+                    tmp[i_] = xy[i_,j];
+                }
+                basestat.samplemoments(tmp, npoints, ref mean, ref variance, ref skewness, ref kurtosis);
+                means[j] = mean;
+                sigmas[j] = Math.Sqrt(variance);
+                if( (double)(sigmas[j])==(double)(0) )
+                {
+                    sigmas[j] = 1;
+                }
+                for(i=0; i<=npoints-1; i++)
+                {
+                    xy[i,j] = (xy[i,j]-means[j])/sigmas[j];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+
+          -- ALGLIB --
+             Copyright 19.05.2008 by Bochkanov Sergey
+        *************************************************************************/
+        public static void dsnormalizec(double[,] xy,
+            int npoints,
+            int nvars,
+            ref int info,
+            ref double[] means,
+            ref double[] sigmas)
+        {
+            int j = 0;
+            double[] tmp = new double[0];
+            double mean = 0;
+            double variance = 0;
+            double skewness = 0;
+            double kurtosis = 0;
+            int i_ = 0;
+
+            info = 0;
+            means = new double[0];
+            sigmas = new double[0];
+
+            
+            //
+            // Test parameters
+            //
+            if( npoints<=0 || nvars<1 )
+            {
+                info = -1;
+                return;
+            }
+            info = 1;
+            
+            //
+            // Standartization
+            //
+            means = new double[nvars-1+1];
+            sigmas = new double[nvars-1+1];
+            tmp = new double[npoints-1+1];
+            for(j=0; j<=nvars-1; j++)
+            {
+                for(i_=0; i_<=npoints-1;i_++)
+                {
+                    tmp[i_] = xy[i_,j];
+                }
+                basestat.samplemoments(tmp, npoints, ref mean, ref variance, ref skewness, ref kurtosis);
+                means[j] = mean;
+                sigmas[j] = Math.Sqrt(variance);
+                if( (double)(sigmas[j])==(double)(0) )
+                {
+                    sigmas[j] = 1;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+
+          -- ALGLIB --
+             Copyright 19.05.2008 by Bochkanov Sergey
+        *************************************************************************/
+        public static double dsgetmeanmindistance(double[,] xy,
+            int npoints,
+            int nvars)
+        {
+            double result = 0;
+            int i = 0;
+            int j = 0;
+            double[] tmp = new double[0];
+            double[] tmp2 = new double[0];
+            double v = 0;
+            int i_ = 0;
+
+            
+            //
+            // Test parameters
+            //
+            if( npoints<=0 || nvars<1 )
+            {
+                result = 0;
+                return result;
+            }
+            
+            //
+            // Process
+            //
+            tmp = new double[npoints-1+1];
+            for(i=0; i<=npoints-1; i++)
+            {
+                tmp[i] = math.maxrealnumber;
+            }
+            tmp2 = new double[nvars-1+1];
+            for(i=0; i<=npoints-1; i++)
+            {
+                for(j=i+1; j<=npoints-1; j++)
+                {
+                    for(i_=0; i_<=nvars-1;i_++)
+                    {
+                        tmp2[i_] = xy[i,i_];
+                    }
+                    for(i_=0; i_<=nvars-1;i_++)
+                    {
+                        tmp2[i_] = tmp2[i_] - xy[j,i_];
+                    }
+                    v = 0.0;
+                    for(i_=0; i_<=nvars-1;i_++)
+                    {
+                        v += tmp2[i_]*tmp2[i_];
+                    }
+                    v = Math.Sqrt(v);
+                    tmp[i] = Math.Min(tmp[i], v);
+                    tmp[j] = Math.Min(tmp[j], v);
+                }
+            }
+            result = 0;
+            for(i=0; i<=npoints-1; i++)
+            {
+                result = result+tmp[i]/npoints;
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+
+          -- ALGLIB --
+             Copyright 19.05.2008 by Bochkanov Sergey
+        *************************************************************************/
+        public static void dstie(ref double[] a,
+            int n,
+            ref int[] ties,
+            ref int tiecount,
+            ref int[] p1,
+            ref int[] p2)
+        {
+            int i = 0;
+            int k = 0;
+            int[] tmp = new int[0];
+
+            ties = new int[0];
+            tiecount = 0;
+            p1 = new int[0];
+            p2 = new int[0];
+
+            
+            //
+            // Special case
+            //
+            if( n<=0 )
+            {
+                tiecount = 0;
+                return;
+            }
+            
+            //
+            // Sort A
+            //
+            tsort.tagsort(ref a, n, ref p1, ref p2);
+            
+            //
+            // Process ties
+            //
+            tiecount = 1;
+            for(i=1; i<=n-1; i++)
+            {
+                if( (double)(a[i])!=(double)(a[i-1]) )
+                {
+                    tiecount = tiecount+1;
+                }
+            }
+            ties = new int[tiecount+1];
+            ties[0] = 0;
+            k = 1;
+            for(i=1; i<=n-1; i++)
+            {
+                if( (double)(a[i])!=(double)(a[i-1]) )
+                {
+                    ties[k] = i;
+                    k = k+1;
+                }
+            }
+            ties[tiecount] = n;
+        }
+
+
+        /*************************************************************************
+
+          -- ALGLIB --
+             Copyright 11.12.2008 by Bochkanov Sergey
+        *************************************************************************/
+        public static void dstiefasti(ref double[] a,
+            ref int[] b,
+            int n,
+            ref int[] ties,
+            ref int tiecount,
+            ref double[] bufr,
+            ref int[] bufi)
+        {
+            int i = 0;
+            int k = 0;
+            int[] tmp = new int[0];
+
+            tiecount = 0;
+
+            
+            //
+            // Special case
+            //
+            if( n<=0 )
+            {
+                tiecount = 0;
+                return;
+            }
+            
+            //
+            // Sort A
+            //
+            tsort.tagsortfasti(ref a, ref b, ref bufr, ref bufi, n);
+            
+            //
+            // Process ties
+            //
+            ties[0] = 0;
+            k = 1;
+            for(i=1; i<=n-1; i++)
+            {
+                if( (double)(a[i])!=(double)(a[i-1]) )
+                {
+                    ties[k] = i;
+                    k = k+1;
+                }
+            }
+            ties[k] = n;
+            tiecount = k;
+        }
+
+
+        /*************************************************************************
+        Optimal binary classification
+
+        Algorithms finds optimal (=with minimal cross-entropy) binary partition.
+        Internal subroutine.
+
+        INPUT PARAMETERS:
+            A       -   array[0..N-1], variable
+            C       -   array[0..N-1], class numbers (0 or 1).
+            N       -   array size
+
+        OUTPUT PARAMETERS:
+            Info    -   completetion code:
+                        * -3, all values of A[] are same (partition is impossible)
+                        * -2, one of C[] is incorrect (<0, >1)
+                        * -1, incorrect pararemets were passed (N<=0).
+                        *  1, OK
+            Threshold-  partiton boundary. Left part contains values which are
+                        strictly less than Threshold. Right part contains values
+                        which are greater than or equal to Threshold.
+            PAL, PBL-   probabilities P(0|v<Threshold) and P(1|v<Threshold)
+            PAR, PBR-   probabilities P(0|v>=Threshold) and P(1|v>=Threshold)
+            CVE     -   cross-validation estimate of cross-entropy
+
+          -- ALGLIB --
+             Copyright 22.05.2008 by Bochkanov Sergey
+        *************************************************************************/
+        public static void dsoptimalsplit2(double[] a,
+            int[] c,
+            int n,
+            ref int info,
+            ref double threshold,
+            ref double pal,
+            ref double pbl,
+            ref double par,
+            ref double pbr,
+            ref double cve)
+        {
+            int i = 0;
+            int t = 0;
+            double s = 0;
+            int[] ties = new int[0];
+            int tiecount = 0;
+            int[] p1 = new int[0];
+            int[] p2 = new int[0];
+            int k = 0;
+            int koptimal = 0;
+            double pak = 0;
+            double pbk = 0;
+            double cvoptimal = 0;
+            double cv = 0;
+
+            a = (double[])a.Clone();
+            c = (int[])c.Clone();
+            info = 0;
+            threshold = 0;
+            pal = 0;
+            pbl = 0;
+            par = 0;
+            pbr = 0;
+            cve = 0;
+
+            
+            //
+            // Test for errors in inputs
+            //
+            if( n<=0 )
+            {
+                info = -1;
+                return;
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                if( c[i]!=0 && c[i]!=1 )
+                {
+                    info = -2;
+                    return;
+                }
+            }
+            info = 1;
+            
+            //
+            // Tie
+            //
+            dstie(ref a, n, ref ties, ref tiecount, ref p1, ref p2);
+            for(i=0; i<=n-1; i++)
+            {
+                if( p2[i]!=i )
+                {
+                    t = c[i];
+                    c[i] = c[p2[i]];
+                    c[p2[i]] = t;
+                }
+            }
+            
+            //
+            // Special case: number of ties is 1.
+            //
+            // NOTE: we assume that P[i,j] equals to 0 or 1,
+            //       intermediate values are not allowed.
+            //
+            if( tiecount==1 )
+            {
+                info = -3;
+                return;
+            }
+            
+            //
+            // General case, number of ties > 1
+            //
+            // NOTE: we assume that P[i,j] equals to 0 or 1,
+            //       intermediate values are not allowed.
+            //
+            pal = 0;
+            pbl = 0;
+            par = 0;
+            pbr = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                if( c[i]==0 )
+                {
+                    par = par+1;
+                }
+                if( c[i]==1 )
+                {
+                    pbr = pbr+1;
+                }
+            }
+            koptimal = -1;
+            cvoptimal = math.maxrealnumber;
+            for(k=0; k<=tiecount-2; k++)
+            {
+                
+                //
+                // first, obtain information about K-th tie which is
+                // moved from R-part to L-part
+                //
+                pak = 0;
+                pbk = 0;
+                for(i=ties[k]; i<=ties[k+1]-1; i++)
+                {
+                    if( c[i]==0 )
+                    {
+                        pak = pak+1;
+                    }
+                    if( c[i]==1 )
+                    {
+                        pbk = pbk+1;
+                    }
+                }
+                
+                //
+                // Calculate cross-validation CE
+                //
+                cv = 0;
+                cv = cv-xlny(pal+pak, (pal+pak)/(pal+pak+pbl+pbk+1));
+                cv = cv-xlny(pbl+pbk, (pbl+pbk)/(pal+pak+1+pbl+pbk));
+                cv = cv-xlny(par-pak, (par-pak)/(par-pak+pbr-pbk+1));
+                cv = cv-xlny(pbr-pbk, (pbr-pbk)/(par-pak+1+pbr-pbk));
+                
+                //
+                // Compare with best
+                //
+                if( (double)(cv)<(double)(cvoptimal) )
+                {
+                    cvoptimal = cv;
+                    koptimal = k;
+                }
+                
+                //
+                // update
+                //
+                pal = pal+pak;
+                pbl = pbl+pbk;
+                par = par-pak;
+                pbr = pbr-pbk;
+            }
+            cve = cvoptimal;
+            threshold = 0.5*(a[ties[koptimal]]+a[ties[koptimal+1]]);
+            pal = 0;
+            pbl = 0;
+            par = 0;
+            pbr = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                if( (double)(a[i])<(double)(threshold) )
+                {
+                    if( c[i]==0 )
+                    {
+                        pal = pal+1;
+                    }
+                    else
+                    {
+                        pbl = pbl+1;
+                    }
+                }
+                else
+                {
+                    if( c[i]==0 )
+                    {
+                        par = par+1;
+                    }
+                    else
+                    {
+                        pbr = pbr+1;
+                    }
+                }
+            }
+            s = pal+pbl;
+            pal = pal/s;
+            pbl = pbl/s;
+            s = par+pbr;
+            par = par/s;
+            pbr = pbr/s;
+        }
+
+
+        /*************************************************************************
+        Optimal partition, internal subroutine. Fast version.
+
+        Accepts:
+            A       array[0..N-1]       array of attributes     array[0..N-1]
+            C       array[0..N-1]       array of class labels
+            TiesBuf array[0..N]         temporaries (ties)
+            CntBuf  array[0..2*NC-1]    temporaries (counts)
+            Alpha                       centering factor (0<=alpha<=1, recommended value - 0.05)
+            BufR    array[0..N-1]       temporaries
+            BufI    array[0..N-1]       temporaries
+
+        Output:
+            Info    error code (">0"=OK, "<0"=bad)
+            RMS     training set RMS error
+            CVRMS   leave-one-out RMS error
+            
+        Note:
+            content of all arrays is changed by subroutine;
+            it doesn't allocate temporaries.
+
+          -- ALGLIB --
+             Copyright 11.12.2008 by Bochkanov Sergey
+        *************************************************************************/
+        public static void dsoptimalsplit2fast(ref double[] a,
+            ref int[] c,
+            ref int[] tiesbuf,
+            ref int[] cntbuf,
+            ref double[] bufr,
+            ref int[] bufi,
+            int n,
+            int nc,
+            double alpha,
+            ref int info,
+            ref double threshold,
+            ref double rms,
+            ref double cvrms)
+        {
+            int i = 0;
+            int k = 0;
+            int cl = 0;
+            int tiecount = 0;
+            double cbest = 0;
+            double cc = 0;
+            int koptimal = 0;
+            int sl = 0;
+            int sr = 0;
+            double v = 0;
+            double w = 0;
+            double x = 0;
+
+            info = 0;
+            threshold = 0;
+            rms = 0;
+            cvrms = 0;
+
+            
+            //
+            // Test for errors in inputs
+            //
+            if( n<=0 || nc<2 )
+            {
+                info = -1;
+                return;
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                if( c[i]<0 || c[i]>=nc )
+                {
+                    info = -2;
+                    return;
+                }
+            }
+            info = 1;
+            
+            //
+            // Tie
+            //
+            dstiefasti(ref a, ref c, n, ref tiesbuf, ref tiecount, ref bufr, ref bufi);
+            
+            //
+            // Special case: number of ties is 1.
+            //
+            if( tiecount==1 )
+            {
+                info = -3;
+                return;
+            }
+            
+            //
+            // General case, number of ties > 1
+            //
+            for(i=0; i<=2*nc-1; i++)
+            {
+                cntbuf[i] = 0;
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                cntbuf[nc+c[i]] = cntbuf[nc+c[i]]+1;
+            }
+            koptimal = -1;
+            threshold = a[n-1];
+            cbest = math.maxrealnumber;
+            sl = 0;
+            sr = n;
+            for(k=0; k<=tiecount-2; k++)
+            {
+                
+                //
+                // first, move Kth tie from right to left
+                //
+                for(i=tiesbuf[k]; i<=tiesbuf[k+1]-1; i++)
+                {
+                    cl = c[i];
+                    cntbuf[cl] = cntbuf[cl]+1;
+                    cntbuf[nc+cl] = cntbuf[nc+cl]-1;
+                }
+                sl = sl+(tiesbuf[k+1]-tiesbuf[k]);
+                sr = sr-(tiesbuf[k+1]-tiesbuf[k]);
+                
+                //
+                // Calculate RMS error
+                //
+                v = 0;
+                for(i=0; i<=nc-1; i++)
+                {
+                    w = cntbuf[i];
+                    v = v+w*math.sqr(w/sl-1);
+                    v = v+(sl-w)*math.sqr(w/sl);
+                    w = cntbuf[nc+i];
+                    v = v+w*math.sqr(w/sr-1);
+                    v = v+(sr-w)*math.sqr(w/sr);
+                }
+                v = Math.Sqrt(v/(nc*n));
+                
+                //
+                // Compare with best
+                //
+                x = (double)(2*sl)/(double)(sl+sr)-1;
+                cc = v*(1-alpha+alpha*math.sqr(x));
+                if( (double)(cc)<(double)(cbest) )
+                {
+                    
+                    //
+                    // store split
+                    //
+                    rms = v;
+                    koptimal = k;
+                    cbest = cc;
+                    
+                    //
+                    // calculate CVRMS error
+                    //
+                    cvrms = 0;
+                    for(i=0; i<=nc-1; i++)
+                    {
+                        if( sl>1 )
+                        {
+                            w = cntbuf[i];
+                            cvrms = cvrms+w*math.sqr((w-1)/(sl-1)-1);
+                            cvrms = cvrms+(sl-w)*math.sqr(w/(sl-1));
+                        }
+                        else
+                        {
+                            w = cntbuf[i];
+                            cvrms = cvrms+w*math.sqr((double)1/(double)nc-1);
+                            cvrms = cvrms+(sl-w)*math.sqr((double)1/(double)nc);
+                        }
+                        if( sr>1 )
+                        {
+                            w = cntbuf[nc+i];
+                            cvrms = cvrms+w*math.sqr((w-1)/(sr-1)-1);
+                            cvrms = cvrms+(sr-w)*math.sqr(w/(sr-1));
+                        }
+                        else
+                        {
+                            w = cntbuf[nc+i];
+                            cvrms = cvrms+w*math.sqr((double)1/(double)nc-1);
+                            cvrms = cvrms+(sr-w)*math.sqr((double)1/(double)nc);
+                        }
+                    }
+                    cvrms = Math.Sqrt(cvrms/(nc*n));
+                }
+            }
+            
+            //
+            // Calculate threshold.
+            // Code is a bit complicated because there can be such
+            // numbers that 0.5(A+B) equals to A or B (if A-B=epsilon)
+            //
+            threshold = 0.5*(a[tiesbuf[koptimal]]+a[tiesbuf[koptimal+1]]);
+            if( (double)(threshold)<=(double)(a[tiesbuf[koptimal]]) )
+            {
+                threshold = a[tiesbuf[koptimal+1]];
+            }
+        }
+
+
+        /*************************************************************************
+        Automatic non-optimal discretization, internal subroutine.
+
+          -- ALGLIB --
+             Copyright 22.05.2008 by Bochkanov Sergey
+        *************************************************************************/
+        public static void dssplitk(double[] a,
+            int[] c,
+            int n,
+            int nc,
+            int kmax,
+            ref int info,
+            ref double[] thresholds,
+            ref int ni,
+            ref double cve)
+        {
+            int i = 0;
+            int j = 0;
+            int j1 = 0;
+            int k = 0;
+            int[] ties = new int[0];
+            int tiecount = 0;
+            int[] p1 = new int[0];
+            int[] p2 = new int[0];
+            int[] cnt = new int[0];
+            double v2 = 0;
+            int bestk = 0;
+            double bestcve = 0;
+            int[] bestsizes = new int[0];
+            double curcve = 0;
+            int[] cursizes = new int[0];
+
+            a = (double[])a.Clone();
+            c = (int[])c.Clone();
+            info = 0;
+            thresholds = new double[0];
+            ni = 0;
+            cve = 0;
+
+            
+            //
+            // Test for errors in inputs
+            //
+            if( (n<=0 || nc<2) || kmax<2 )
+            {
+                info = -1;
+                return;
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                if( c[i]<0 || c[i]>=nc )
+                {
+                    info = -2;
+                    return;
+                }
+            }
+            info = 1;
+            
+            //
+            // Tie
+            //
+            dstie(ref a, n, ref ties, ref tiecount, ref p1, ref p2);
+            for(i=0; i<=n-1; i++)
+            {
+                if( p2[i]!=i )
+                {
+                    k = c[i];
+                    c[i] = c[p2[i]];
+                    c[p2[i]] = k;
+                }
+            }
+            
+            //
+            // Special cases
+            //
+            if( tiecount==1 )
+            {
+                info = -3;
+                return;
+            }
+            
+            //
+            // General case:
+            // 0. allocate arrays
+            //
+            kmax = Math.Min(kmax, tiecount);
+            bestsizes = new int[kmax-1+1];
+            cursizes = new int[kmax-1+1];
+            cnt = new int[nc-1+1];
+            
+            //
+            // General case:
+            // 1. prepare "weak" solution (two subintervals, divided at median)
+            //
+            v2 = math.maxrealnumber;
+            j = -1;
+            for(i=1; i<=tiecount-1; i++)
+            {
+                if( (double)(Math.Abs(ties[i]-0.5*(n-1)))<(double)(v2) )
+                {
+                    v2 = Math.Abs(ties[i]-0.5*n);
+                    j = i;
+                }
+            }
+            alglib.ap.assert(j>0, "DSSplitK: internal error #1!");
+            bestk = 2;
+            bestsizes[0] = ties[j];
+            bestsizes[1] = n-j;
+            bestcve = 0;
+            for(i=0; i<=nc-1; i++)
+            {
+                cnt[i] = 0;
+            }
+            for(i=0; i<=j-1; i++)
+            {
+                tieaddc(c, ties, i, nc, ref cnt);
+            }
+            bestcve = bestcve+getcv(cnt, nc);
+            for(i=0; i<=nc-1; i++)
+            {
+                cnt[i] = 0;
+            }
+            for(i=j; i<=tiecount-1; i++)
+            {
+                tieaddc(c, ties, i, nc, ref cnt);
+            }
+            bestcve = bestcve+getcv(cnt, nc);
+            
+            //
+            // General case:
+            // 2. Use greedy algorithm to find sub-optimal split in O(KMax*N) time
+            //
+            for(k=2; k<=kmax; k++)
+            {
+                
+                //
+                // Prepare greedy K-interval split
+                //
+                for(i=0; i<=k-1; i++)
+                {
+                    cursizes[i] = 0;
+                }
+                i = 0;
+                j = 0;
+                while( j<=tiecount-1 && i<=k-1 )
+                {
+                    
+                    //
+                    // Rule: I-th bin is empty, fill it
+                    //
+                    if( cursizes[i]==0 )
+                    {
+                        cursizes[i] = ties[j+1]-ties[j];
+                        j = j+1;
+                        continue;
+                    }
+                    
+                    //
+                    // Rule: (K-1-I) bins left, (K-1-I) ties left (1 tie per bin); next bin
+                    //
+                    if( tiecount-j==k-1-i )
+                    {
+                        i = i+1;
+                        continue;
+                    }
+                    
+                    //
+                    // Rule: last bin, always place in current
+                    //
+                    if( i==k-1 )
+                    {
+                        cursizes[i] = cursizes[i]+ties[j+1]-ties[j];
+                        j = j+1;
+                        continue;
+                    }
+                    
+                    //
+                    // Place J-th tie in I-th bin, or leave for I+1-th bin.
+                    //
+                    if( (double)(Math.Abs(cursizes[i]+ties[j+1]-ties[j]-(double)n/(double)k))<(double)(Math.Abs(cursizes[i]-(double)n/(double)k)) )
+                    {
+                        cursizes[i] = cursizes[i]+ties[j+1]-ties[j];
+                        j = j+1;
+                    }
+                    else
+                    {
+                        i = i+1;
+                    }
+                }
+                alglib.ap.assert(cursizes[k-1]!=0 && j==tiecount, "DSSplitK: internal error #1");
+                
+                //
+                // Calculate CVE
+                //
+                curcve = 0;
+                j = 0;
+                for(i=0; i<=k-1; i++)
+                {
+                    for(j1=0; j1<=nc-1; j1++)
+                    {
+                        cnt[j1] = 0;
+                    }
+                    for(j1=j; j1<=j+cursizes[i]-1; j1++)
+                    {
+                        cnt[c[j1]] = cnt[c[j1]]+1;
+                    }
+                    curcve = curcve+getcv(cnt, nc);
+                    j = j+cursizes[i];
+                }
+                
+                //
+                // Choose best variant
+                //
+                if( (double)(curcve)<(double)(bestcve) )
+                {
+                    for(i=0; i<=k-1; i++)
+                    {
+                        bestsizes[i] = cursizes[i];
+                    }
+                    bestcve = curcve;
+                    bestk = k;
+                }
+            }
+            
+            //
+            // Transform from sizes to thresholds
+            //
+            cve = bestcve;
+            ni = bestk;
+            thresholds = new double[ni-2+1];
+            j = bestsizes[0];
+            for(i=1; i<=bestk-1; i++)
+            {
+                thresholds[i-1] = 0.5*(a[j-1]+a[j]);
+                j = j+bestsizes[i];
+            }
+        }
+
+
+        /*************************************************************************
+        Automatic optimal discretization, internal subroutine.
+
+          -- ALGLIB --
+             Copyright 22.05.2008 by Bochkanov Sergey
+        *************************************************************************/
+        public static void dsoptimalsplitk(double[] a,
+            int[] c,
+            int n,
+            int nc,
+            int kmax,
+            ref int info,
+            ref double[] thresholds,
+            ref int ni,
+            ref double cve)
+        {
+            int i = 0;
+            int j = 0;
+            int s = 0;
+            int jl = 0;
+            int jr = 0;
+            double v2 = 0;
+            int[] ties = new int[0];
+            int tiecount = 0;
+            int[] p1 = new int[0];
+            int[] p2 = new int[0];
+            double cvtemp = 0;
+            int[] cnt = new int[0];
+            int[] cnt2 = new int[0];
+            double[,] cv = new double[0,0];
+            int[,] splits = new int[0,0];
+            int k = 0;
+            int koptimal = 0;
+            double cvoptimal = 0;
+
+            a = (double[])a.Clone();
+            c = (int[])c.Clone();
+            info = 0;
+            thresholds = new double[0];
+            ni = 0;
+            cve = 0;
+
+            
+            //
+            // Test for errors in inputs
+            //
+            if( (n<=0 || nc<2) || kmax<2 )
+            {
+                info = -1;
+                return;
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                if( c[i]<0 || c[i]>=nc )
+                {
+                    info = -2;
+                    return;
+                }
+            }
+            info = 1;
+            
+            //
+            // Tie
+            //
+            dstie(ref a, n, ref ties, ref tiecount, ref p1, ref p2);
+            for(i=0; i<=n-1; i++)
+            {
+                if( p2[i]!=i )
+                {
+                    k = c[i];
+                    c[i] = c[p2[i]];
+                    c[p2[i]] = k;
+                }
+            }
+            
+            //
+            // Special cases
+            //
+            if( tiecount==1 )
+            {
+                info = -3;
+                return;
+            }
+            
+            //
+            // General case
+            // Use dynamic programming to find best split in O(KMax*NC*TieCount^2) time
+            //
+            kmax = Math.Min(kmax, tiecount);
+            cv = new double[kmax-1+1, tiecount-1+1];
+            splits = new int[kmax-1+1, tiecount-1+1];
+            cnt = new int[nc-1+1];
+            cnt2 = new int[nc-1+1];
+            for(j=0; j<=nc-1; j++)
+            {
+                cnt[j] = 0;
+            }
+            for(j=0; j<=tiecount-1; j++)
+            {
+                tieaddc(c, ties, j, nc, ref cnt);
+                splits[0,j] = 0;
+                cv[0,j] = getcv(cnt, nc);
+            }
+            for(k=1; k<=kmax-1; k++)
+            {
+                for(j=0; j<=nc-1; j++)
+                {
+                    cnt[j] = 0;
+                }
+                
+                //
+                // Subtask size J in [K..TieCount-1]:
+                // optimal K-splitting on ties from 0-th to J-th.
+                //
+                for(j=k; j<=tiecount-1; j++)
+                {
+                    
+                    //
+                    // Update Cnt - let it contain classes of ties from K-th to J-th
+                    //
+                    tieaddc(c, ties, j, nc, ref cnt);
+                    
+                    //
+                    // Search for optimal split point S in [K..J]
+                    //
+                    for(i=0; i<=nc-1; i++)
+                    {
+                        cnt2[i] = cnt[i];
+                    }
+                    cv[k,j] = cv[k-1,j-1]+getcv(cnt2, nc);
+                    splits[k,j] = j;
+                    for(s=k+1; s<=j; s++)
+                    {
+                        
+                        //
+                        // Update Cnt2 - let it contain classes of ties from S-th to J-th
+                        //
+                        tiesubc(c, ties, s-1, nc, ref cnt2);
+                        
+                        //
+                        // Calculate CVE
+                        //
+                        cvtemp = cv[k-1,s-1]+getcv(cnt2, nc);
+                        if( (double)(cvtemp)<(double)(cv[k,j]) )
+                        {
+                            cv[k,j] = cvtemp;
+                            splits[k,j] = s;
+                        }
+                    }
+                }
+            }
+            
+            //
+            // Choose best partition, output result
+            //
+            koptimal = -1;
+            cvoptimal = math.maxrealnumber;
+            for(k=0; k<=kmax-1; k++)
+            {
+                if( (double)(cv[k,tiecount-1])<(double)(cvoptimal) )
+                {
+                    cvoptimal = cv[k,tiecount-1];
+                    koptimal = k;
+                }
+            }
+            alglib.ap.assert(koptimal>=0, "DSOptimalSplitK: internal error #1!");
+            if( koptimal==0 )
+            {
+                
+                //
+                // Special case: best partition is one big interval.
+                // Even 2-partition is not better.
+                // This is possible when dealing with "weak" predictor variables.
+                //
+                // Make binary split as close to the median as possible.
+                //
+                v2 = math.maxrealnumber;
+                j = -1;
+                for(i=1; i<=tiecount-1; i++)
+                {
+                    if( (double)(Math.Abs(ties[i]-0.5*(n-1)))<(double)(v2) )
+                    {
+                        v2 = Math.Abs(ties[i]-0.5*(n-1));
+                        j = i;
+                    }
+                }
+                alglib.ap.assert(j>0, "DSOptimalSplitK: internal error #2!");
+                thresholds = new double[0+1];
+                thresholds[0] = 0.5*(a[ties[j-1]]+a[ties[j]]);
+                ni = 2;
+                cve = 0;
+                for(i=0; i<=nc-1; i++)
+                {
+                    cnt[i] = 0;
+                }
+                for(i=0; i<=j-1; i++)
+                {
+                    tieaddc(c, ties, i, nc, ref cnt);
+                }
+                cve = cve+getcv(cnt, nc);
+                for(i=0; i<=nc-1; i++)
+                {
+                    cnt[i] = 0;
+                }
+                for(i=j; i<=tiecount-1; i++)
+                {
+                    tieaddc(c, ties, i, nc, ref cnt);
+                }
+                cve = cve+getcv(cnt, nc);
+            }
+            else
+            {
+                
+                //
+                // General case: 2 or more intervals
+                //
+                thresholds = new double[koptimal-1+1];
+                ni = koptimal+1;
+                cve = cv[koptimal,tiecount-1];
+                jl = splits[koptimal,tiecount-1];
+                jr = tiecount-1;
+                for(k=koptimal; k>=1; k--)
+                {
+                    thresholds[k-1] = 0.5*(a[ties[jl-1]]+a[ties[jl]]);
+                    jr = jl-1;
+                    jl = splits[k-1,jl-1];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Internal function
+        *************************************************************************/
+        private static double xlny(double x,
+            double y)
+        {
+            double result = 0;
+
+            if( (double)(x)==(double)(0) )
+            {
+                result = 0;
+            }
+            else
+            {
+                result = x*Math.Log(y);
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        Internal function,
+        returns number of samples of class I in Cnt[I]
+        *************************************************************************/
+        private static double getcv(int[] cnt,
+            int nc)
+        {
+            double result = 0;
+            int i = 0;
+            double s = 0;
+
+            s = 0;
+            for(i=0; i<=nc-1; i++)
+            {
+                s = s+cnt[i];
+            }
+            result = 0;
+            for(i=0; i<=nc-1; i++)
+            {
+                result = result-xlny(cnt[i], cnt[i]/(s+nc-1));
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        Internal function, adds number of samples of class I in tie NTie to Cnt[I]
+        *************************************************************************/
+        private static void tieaddc(int[] c,
+            int[] ties,
+            int ntie,
+            int nc,
+            ref int[] cnt)
+        {
+            int i = 0;
+
+            for(i=ties[ntie]; i<=ties[ntie+1]-1; i++)
+            {
+                cnt[c[i]] = cnt[c[i]]+1;
+            }
+        }
+
+
+        /*************************************************************************
+        Internal function, subtracts number of samples of class I in tie NTie to Cnt[I]
+        *************************************************************************/
+        private static void tiesubc(int[] c,
+            int[] ties,
+            int ntie,
+            int nc,
+            ref int[] cnt)
+        {
+            int i = 0;
+
+            for(i=ties[ntie]; i<=ties[ntie+1]-1; i++)
+            {
+                cnt[c[i]] = cnt[c[i]]-1;
+            }
+        }
+
+
+    }
+    public class dforest
+    {
+        public class decisionforest
+        {
+            public int nvars;
+            public int nclasses;
+            public int ntrees;
+            public int bufsize;
+            public double[] trees;
+            public decisionforest()
+            {
+                trees = new double[0];
+            }
+        };
+
+
+        public class dfreport
+        {
+            public double relclserror;
+            public double avgce;
+            public double rmserror;
+            public double avgerror;
+            public double avgrelerror;
+            public double oobrelclserror;
+            public double oobavgce;
+            public double oobrmserror;
+            public double oobavgerror;
+            public double oobavgrelerror;
+        };
+
+
+        public class dfinternalbuffers
+        {
+            public double[] treebuf;
+            public int[] idxbuf;
+            public double[] tmpbufr;
+            public double[] tmpbufr2;
+            public int[] tmpbufi;
+            public int[] classibuf;
+            public double[] sortrbuf;
+            public double[] sortrbuf2;
+            public int[] sortibuf;
+            public int[] varpool;
+            public bool[] evsbin;
+            public double[] evssplits;
+            public dfinternalbuffers()
+            {
+                treebuf = new double[0];
+                idxbuf = new int[0];
+                tmpbufr = new double[0];
+                tmpbufr2 = new double[0];
+                tmpbufi = new int[0];
+                classibuf = new int[0];
+                sortrbuf = new double[0];
+                sortrbuf2 = new double[0];
+                sortibuf = new int[0];
+                varpool = new int[0];
+                evsbin = new bool[0];
+                evssplits = new double[0];
+            }
+        };
+
+
+
+
+        public const int innernodewidth = 3;
+        public const int leafnodewidth = 2;
+        public const int dfusestrongsplits = 1;
+        public const int dfuseevs = 2;
+        public const int dffirstversion = 0;
+
+
+        /*************************************************************************
+        This subroutine builds random decision forest.
+
+        INPUT PARAMETERS:
+            XY          -   training set
+            NPoints     -   training set size, NPoints>=1
+            NVars       -   number of independent variables, NVars>=1
+            NClasses    -   task type:
+                            * NClasses=1 - regression task with one
+                                           dependent variable
+                            * NClasses>1 - classification task with
+                                           NClasses classes.
+            NTrees      -   number of trees in a forest, NTrees>=1.
+                            recommended values: 50-100.
+            R           -   percent of a training set used to build
+                            individual trees. 0<R<=1.
+                            recommended values: 0.1 <= R <= 0.66.
+
+        OUTPUT PARAMETERS:
+            Info        -   return code:
+                            * -2, if there is a point with class number
+                                  outside of [0..NClasses-1].
+                            * -1, if incorrect parameters was passed
+                                  (NPoints<1, NVars<1, NClasses<1, NTrees<1, R<=0
+                                  or R>1).
+                            *  1, if task has been solved
+            DF          -   model built
+            Rep         -   training report, contains error on a training set
+                            and out-of-bag estimates of generalization error.
+
+          -- ALGLIB --
+             Copyright 19.02.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void dfbuildrandomdecisionforest(double[,] xy,
+            int npoints,
+            int nvars,
+            int nclasses,
+            int ntrees,
+            double r,
+            ref int info,
+            decisionforest df,
+            dfreport rep)
+        {
+            int samplesize = 0;
+
+            info = 0;
+
+            if( (double)(r)<=(double)(0) || (double)(r)>(double)(1) )
+            {
+                info = -1;
+                return;
+            }
+            samplesize = Math.Max((int)Math.Round(r*npoints), 1);
+            dfbuildinternal(xy, npoints, nvars, nclasses, ntrees, samplesize, Math.Max(nvars/2, 1), dfusestrongsplits+dfuseevs, ref info, df, rep);
+        }
+
+
+        /*************************************************************************
+        This subroutine builds random decision forest.
+        This function gives ability to tune number of variables used when choosing
+        best split.
+
+        INPUT PARAMETERS:
+            XY          -   training set
+            NPoints     -   training set size, NPoints>=1
+            NVars       -   number of independent variables, NVars>=1
+            NClasses    -   task type:
+                            * NClasses=1 - regression task with one
+                                           dependent variable
+                            * NClasses>1 - classification task with
+                                           NClasses classes.
+            NTrees      -   number of trees in a forest, NTrees>=1.
+                            recommended values: 50-100.
+            NRndVars    -   number of variables used when choosing best split
+            R           -   percent of a training set used to build
+                            individual trees. 0<R<=1.
+                            recommended values: 0.1 <= R <= 0.66.
+
+        OUTPUT PARAMETERS:
+            Info        -   return code:
+                            * -2, if there is a point with class number
+                                  outside of [0..NClasses-1].
+                            * -1, if incorrect parameters was passed
+                                  (NPoints<1, NVars<1, NClasses<1, NTrees<1, R<=0
+                                  or R>1).
+                            *  1, if task has been solved
+            DF          -   model built
+            Rep         -   training report, contains error on a training set
+                            and out-of-bag estimates of generalization error.
+
+          -- ALGLIB --
+             Copyright 19.02.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void dfbuildrandomdecisionforestx1(double[,] xy,
+            int npoints,
+            int nvars,
+            int nclasses,
+            int ntrees,
+            int nrndvars,
+            double r,
+            ref int info,
+            decisionforest df,
+            dfreport rep)
+        {
+            int samplesize = 0;
+
+            info = 0;
+
+            if( (double)(r)<=(double)(0) || (double)(r)>(double)(1) )
+            {
+                info = -1;
+                return;
+            }
+            if( nrndvars<=0 || nrndvars>nvars )
+            {
+                info = -1;
+                return;
+            }
+            samplesize = Math.Max((int)Math.Round(r*npoints), 1);
+            dfbuildinternal(xy, npoints, nvars, nclasses, ntrees, samplesize, nrndvars, dfusestrongsplits+dfuseevs, ref info, df, rep);
+        }
+
+
+        public static void dfbuildinternal(double[,] xy,
+            int npoints,
+            int nvars,
+            int nclasses,
+            int ntrees,
+            int samplesize,
+            int nfeatures,
+            int flags,
+            ref int info,
+            decisionforest df,
+            dfreport rep)
+        {
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int tmpi = 0;
+            int lasttreeoffs = 0;
+            int offs = 0;
+            int ooboffs = 0;
+            int treesize = 0;
+            int nvarsinpool = 0;
+            bool useevs = new bool();
+            dfinternalbuffers bufs = new dfinternalbuffers();
+            int[] permbuf = new int[0];
+            double[] oobbuf = new double[0];
+            int[] oobcntbuf = new int[0];
+            double[,] xys = new double[0,0];
+            double[] x = new double[0];
+            double[] y = new double[0];
+            int oobcnt = 0;
+            int oobrelcnt = 0;
+            double v = 0;
+            double vmin = 0;
+            double vmax = 0;
+            bool bflag = new bool();
+            int i_ = 0;
+            int i1_ = 0;
+
+            info = 0;
+
+            
+            //
+            // Test for inputs
+            //
+            if( (((((npoints<1 || samplesize<1) || samplesize>npoints) || nvars<1) || nclasses<1) || ntrees<1) || nfeatures<1 )
+            {
+                info = -1;
+                return;
+            }
+            if( nclasses>1 )
+            {
+                for(i=0; i<=npoints-1; i++)
+                {
+                    if( (int)Math.Round(xy[i,nvars])<0 || (int)Math.Round(xy[i,nvars])>=nclasses )
+                    {
+                        info = -2;
+                        return;
+                    }
+                }
+            }
+            info = 1;
+            
+            //
+            // Flags
+            //
+            useevs = flags/dfuseevs%2!=0;
+            
+            //
+            // Allocate data, prepare header
+            //
+            treesize = 1+innernodewidth*(samplesize-1)+leafnodewidth*samplesize;
+            permbuf = new int[npoints-1+1];
+            bufs.treebuf = new double[treesize-1+1];
+            bufs.idxbuf = new int[npoints-1+1];
+            bufs.tmpbufr = new double[npoints-1+1];
+            bufs.tmpbufr2 = new double[npoints-1+1];
+            bufs.tmpbufi = new int[npoints-1+1];
+            bufs.sortrbuf = new double[npoints];
+            bufs.sortrbuf2 = new double[npoints];
+            bufs.sortibuf = new int[npoints];
+            bufs.varpool = new int[nvars-1+1];
+            bufs.evsbin = new bool[nvars-1+1];
+            bufs.evssplits = new double[nvars-1+1];
+            bufs.classibuf = new int[2*nclasses-1+1];
+            oobbuf = new double[nclasses*npoints-1+1];
+            oobcntbuf = new int[npoints-1+1];
+            df.trees = new double[ntrees*treesize-1+1];
+            xys = new double[samplesize-1+1, nvars+1];
+            x = new double[nvars-1+1];
+            y = new double[nclasses-1+1];
+            for(i=0; i<=npoints-1; i++)
+            {
+                permbuf[i] = i;
+            }
+            for(i=0; i<=npoints*nclasses-1; i++)
+            {
+                oobbuf[i] = 0;
+            }
+            for(i=0; i<=npoints-1; i++)
+            {
+                oobcntbuf[i] = 0;
+            }
+            
+            //
+            // Prepare variable pool and EVS (extended variable selection/splitting) buffers
+            // (whether EVS is turned on or not):
+            // 1. detect binary variables and pre-calculate splits for them
+            // 2. detect variables with non-distinct values and exclude them from pool
+            //
+            for(i=0; i<=nvars-1; i++)
+            {
+                bufs.varpool[i] = i;
+            }
+            nvarsinpool = nvars;
+            if( useevs )
+            {
+                for(j=0; j<=nvars-1; j++)
+                {
+                    vmin = xy[0,j];
+                    vmax = vmin;
+                    for(i=0; i<=npoints-1; i++)
+                    {
+                        v = xy[i,j];
+                        vmin = Math.Min(vmin, v);
+                        vmax = Math.Max(vmax, v);
+                    }
+                    if( (double)(vmin)==(double)(vmax) )
+                    {
+                        
+                        //
+                        // exclude variable from pool
+                        //
+                        bufs.varpool[j] = bufs.varpool[nvarsinpool-1];
+                        bufs.varpool[nvarsinpool-1] = -1;
+                        nvarsinpool = nvarsinpool-1;
+                        continue;
+                    }
+                    bflag = false;
+                    for(i=0; i<=npoints-1; i++)
+                    {
+                        v = xy[i,j];
+                        if( (double)(v)!=(double)(vmin) && (double)(v)!=(double)(vmax) )
+                        {
+                            bflag = true;
+                            break;
+                        }
+                    }
+                    if( bflag )
+                    {
+                        
+                        //
+                        // non-binary variable
+                        //
+                        bufs.evsbin[j] = false;
+                    }
+                    else
+                    {
+                        
+                        //
+                        // Prepare
+                        //
+                        bufs.evsbin[j] = true;
+                        bufs.evssplits[j] = 0.5*(vmin+vmax);
+                        if( (double)(bufs.evssplits[j])<=(double)(vmin) )
+                        {
+                            bufs.evssplits[j] = vmax;
+                        }
+                    }
+                }
+            }
+            
+            //
+            // RANDOM FOREST FORMAT
+            // W[0]         -   size of array
+            // W[1]         -   version number
+            // W[2]         -   NVars
+            // W[3]         -   NClasses (1 for regression)
+            // W[4]         -   NTrees
+            // W[5]         -   trees offset
+            //
+            //
+            // TREE FORMAT
+            // W[Offs]      -   size of sub-array
+            //     node info:
+            // W[K+0]       -   variable number        (-1 for leaf mode)
+            // W[K+1]       -   threshold              (class/value for leaf node)
+            // W[K+2]       -   ">=" branch index      (absent for leaf node)
+            //
+            //
+            df.nvars = nvars;
+            df.nclasses = nclasses;
+            df.ntrees = ntrees;
+            
+            //
+            // Build forest
+            //
+            offs = 0;
+            for(i=0; i<=ntrees-1; i++)
+            {
+                
+                //
+                // Prepare sample
+                //
+                for(k=0; k<=samplesize-1; k++)
+                {
+                    j = k+math.randominteger(npoints-k);
+                    tmpi = permbuf[k];
+                    permbuf[k] = permbuf[j];
+                    permbuf[j] = tmpi;
+                    j = permbuf[k];
+                    for(i_=0; i_<=nvars;i_++)
+                    {
+                        xys[k,i_] = xy[j,i_];
+                    }
+                }
+                
+                //
+                // build tree, copy
+                //
+                dfbuildtree(xys, samplesize, nvars, nclasses, nfeatures, nvarsinpool, flags, bufs);
+                j = (int)Math.Round(bufs.treebuf[0]);
+                i1_ = (0) - (offs);
+                for(i_=offs; i_<=offs+j-1;i_++)
+                {
+                    df.trees[i_] = bufs.treebuf[i_+i1_];
+                }
+                lasttreeoffs = offs;
+                offs = offs+j;
+                
+                //
+                // OOB estimates
+                //
+                for(k=samplesize; k<=npoints-1; k++)
+                {
+                    for(j=0; j<=nclasses-1; j++)
+                    {
+                        y[j] = 0;
+                    }
+                    j = permbuf[k];
+                    for(i_=0; i_<=nvars-1;i_++)
+                    {
+                        x[i_] = xy[j,i_];
+                    }
+                    dfprocessinternal(df, lasttreeoffs, x, ref y);
+                    i1_ = (0) - (j*nclasses);
+                    for(i_=j*nclasses; i_<=(j+1)*nclasses-1;i_++)
+                    {
+                        oobbuf[i_] = oobbuf[i_] + y[i_+i1_];
+                    }
+                    oobcntbuf[j] = oobcntbuf[j]+1;
+                }
+            }
+            df.bufsize = offs;
+            
+            //
+            // Normalize OOB results
+            //
+            for(i=0; i<=npoints-1; i++)
+            {
+                if( oobcntbuf[i]!=0 )
+                {
+                    v = (double)1/(double)oobcntbuf[i];
+                    for(i_=i*nclasses; i_<=i*nclasses+nclasses-1;i_++)
+                    {
+                        oobbuf[i_] = v*oobbuf[i_];
+                    }
+                }
+            }
+            
+            //
+            // Calculate training set estimates
+            //
+            rep.relclserror = dfrelclserror(df, xy, npoints);
+            rep.avgce = dfavgce(df, xy, npoints);
+            rep.rmserror = dfrmserror(df, xy, npoints);
+            rep.avgerror = dfavgerror(df, xy, npoints);
+            rep.avgrelerror = dfavgrelerror(df, xy, npoints);
+            
+            //
+            // Calculate OOB estimates.
+            //
+            rep.oobrelclserror = 0;
+            rep.oobavgce = 0;
+            rep.oobrmserror = 0;
+            rep.oobavgerror = 0;
+            rep.oobavgrelerror = 0;
+            oobcnt = 0;
+            oobrelcnt = 0;
+            for(i=0; i<=npoints-1; i++)
+            {
+                if( oobcntbuf[i]!=0 )
+                {
+                    ooboffs = i*nclasses;
+                    if( nclasses>1 )
+                    {
+                        
+                        //
+                        // classification-specific code
+                        //
+                        k = (int)Math.Round(xy[i,nvars]);
+                        tmpi = 0;
+                        for(j=1; j<=nclasses-1; j++)
+                        {
+                            if( (double)(oobbuf[ooboffs+j])>(double)(oobbuf[ooboffs+tmpi]) )
+                            {
+                                tmpi = j;
+                            }
+                        }
+                        if( tmpi!=k )
+                        {
+                            rep.oobrelclserror = rep.oobrelclserror+1;
+                        }
+                        if( (double)(oobbuf[ooboffs+k])!=(double)(0) )
+                        {
+                            rep.oobavgce = rep.oobavgce-Math.Log(oobbuf[ooboffs+k]);
+                        }
+                        else
+                        {
+                            rep.oobavgce = rep.oobavgce-Math.Log(math.minrealnumber);
+                        }
+                        for(j=0; j<=nclasses-1; j++)
+                        {
+                            if( j==k )
+                            {
+                                rep.oobrmserror = rep.oobrmserror+math.sqr(oobbuf[ooboffs+j]-1);
+                                rep.oobavgerror = rep.oobavgerror+Math.Abs(oobbuf[ooboffs+j]-1);
+                                rep.oobavgrelerror = rep.oobavgrelerror+Math.Abs(oobbuf[ooboffs+j]-1);
+                                oobrelcnt = oobrelcnt+1;
+                            }
+                            else
+                            {
+                                rep.oobrmserror = rep.oobrmserror+math.sqr(oobbuf[ooboffs+j]);
+                                rep.oobavgerror = rep.oobavgerror+Math.Abs(oobbuf[ooboffs+j]);
+                            }
+                        }
+                    }
+                    else
+                    {
+                        
+                        //
+                        // regression-specific code
+                        //
+                        rep.oobrmserror = rep.oobrmserror+math.sqr(oobbuf[ooboffs]-xy[i,nvars]);
+                        rep.oobavgerror = rep.oobavgerror+Math.Abs(oobbuf[ooboffs]-xy[i,nvars]);
+                        if( (double)(xy[i,nvars])!=(double)(0) )
+                        {
+                            rep.oobavgrelerror = rep.oobavgrelerror+Math.Abs((oobbuf[ooboffs]-xy[i,nvars])/xy[i,nvars]);
+                            oobrelcnt = oobrelcnt+1;
+                        }
+                    }
+                    
+                    //
+                    // update OOB estimates count.
+                    //
+                    oobcnt = oobcnt+1;
+                }
+            }
+            if( oobcnt>0 )
+            {
+                rep.oobrelclserror = rep.oobrelclserror/oobcnt;
+                rep.oobavgce = rep.oobavgce/oobcnt;
+                rep.oobrmserror = Math.Sqrt(rep.oobrmserror/(oobcnt*nclasses));
+                rep.oobavgerror = rep.oobavgerror/(oobcnt*nclasses);
+                if( oobrelcnt>0 )
+                {
+                    rep.oobavgrelerror = rep.oobavgrelerror/oobrelcnt;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Procesing
+
+        INPUT PARAMETERS:
+            DF      -   decision forest model
+            X       -   input vector,  array[0..NVars-1].
+
+        OUTPUT PARAMETERS:
+            Y       -   result. Regression estimate when solving regression  task,
+                        vector of posterior probabilities for classification task.
+
+        See also DFProcessI.
+
+          -- ALGLIB --
+             Copyright 16.02.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void dfprocess(decisionforest df,
+            double[] x,
+            ref double[] y)
+        {
+            int offs = 0;
+            int i = 0;
+            double v = 0;
+            int i_ = 0;
+
+            
+            //
+            // Proceed
+            //
+            if( alglib.ap.len(y)<df.nclasses )
+            {
+                y = new double[df.nclasses];
+            }
+            offs = 0;
+            for(i=0; i<=df.nclasses-1; i++)
+            {
+                y[i] = 0;
+            }
+            for(i=0; i<=df.ntrees-1; i++)
+            {
+                
+                //
+                // Process basic tree
+                //
+                dfprocessinternal(df, offs, x, ref y);
+                
+                //
+                // Next tree
+                //
+                offs = offs+(int)Math.Round(df.trees[offs]);
+            }
+            v = (double)1/(double)df.ntrees;
+            for(i_=0; i_<=df.nclasses-1;i_++)
+            {
+                y[i_] = v*y[i_];
+            }
+        }
+
+
+        /*************************************************************************
+        'interactive' variant of DFProcess for languages like Python which support
+        constructs like "Y = DFProcessI(DF,X)" and interactive mode of interpreter
+
+        This function allocates new array on each call,  so  it  is  significantly
+        slower than its 'non-interactive' counterpart, but it is  more  convenient
+        when you call it from command line.
+
+          -- ALGLIB --
+             Copyright 28.02.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void dfprocessi(decisionforest df,
+            double[] x,
+            ref double[] y)
+        {
+            y = new double[0];
+
+            dfprocess(df, x, ref y);
+        }
+
+
+        /*************************************************************************
+        Relative classification error on the test set
+
+        INPUT PARAMETERS:
+            DF      -   decision forest model
+            XY      -   test set
+            NPoints -   test set size
+
+        RESULT:
+            percent of incorrectly classified cases.
+            Zero if model solves regression task.
+
+          -- ALGLIB --
+             Copyright 16.02.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static double dfrelclserror(decisionforest df,
+            double[,] xy,
+            int npoints)
+        {
+            double result = 0;
+
+            result = (double)dfclserror(df, xy, npoints)/(double)npoints;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Average cross-entropy (in bits per element) on the test set
+
+        INPUT PARAMETERS:
+            DF      -   decision forest model
+            XY      -   test set
+            NPoints -   test set size
+
+        RESULT:
+            CrossEntropy/(NPoints*LN(2)).
+            Zero if model solves regression task.
+
+          -- ALGLIB --
+             Copyright 16.02.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static double dfavgce(decisionforest df,
+            double[,] xy,
+            int npoints)
+        {
+            double result = 0;
+            double[] x = new double[0];
+            double[] y = new double[0];
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int tmpi = 0;
+            int i_ = 0;
+
+            x = new double[df.nvars-1+1];
+            y = new double[df.nclasses-1+1];
+            result = 0;
+            for(i=0; i<=npoints-1; i++)
+            {
+                for(i_=0; i_<=df.nvars-1;i_++)
+                {
+                    x[i_] = xy[i,i_];
+                }
+                dfprocess(df, x, ref y);
+                if( df.nclasses>1 )
+                {
+                    
+                    //
+                    // classification-specific code
+                    //
+                    k = (int)Math.Round(xy[i,df.nvars]);
+                    tmpi = 0;
+                    for(j=1; j<=df.nclasses-1; j++)
+                    {
+                        if( (double)(y[j])>(double)(y[tmpi]) )
+                        {
+                            tmpi = j;
+                        }
+                    }
+                    if( (double)(y[k])!=(double)(0) )
+                    {
+                        result = result-Math.Log(y[k]);
+                    }
+                    else
+                    {
+                        result = result-Math.Log(math.minrealnumber);
+                    }
+                }
+            }
+            result = result/npoints;
+            return result;
+        }
+
+
+        /*************************************************************************
+        RMS error on the test set
+
+        INPUT PARAMETERS:
+            DF      -   decision forest model
+            XY      -   test set
+            NPoints -   test set size
+
+        RESULT:
+            root mean square error.
+            Its meaning for regression task is obvious. As for
+            classification task, RMS error means error when estimating posterior
+            probabilities.
+
+          -- ALGLIB --
+             Copyright 16.02.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static double dfrmserror(decisionforest df,
+            double[,] xy,
+            int npoints)
+        {
+            double result = 0;
+            double[] x = new double[0];
+            double[] y = new double[0];
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int tmpi = 0;
+            int i_ = 0;
+
+            x = new double[df.nvars-1+1];
+            y = new double[df.nclasses-1+1];
+            result = 0;
+            for(i=0; i<=npoints-1; i++)
+            {
+                for(i_=0; i_<=df.nvars-1;i_++)
+                {
+                    x[i_] = xy[i,i_];
+                }
+                dfprocess(df, x, ref y);
+                if( df.nclasses>1 )
+                {
+                    
+                    //
+                    // classification-specific code
+                    //
+                    k = (int)Math.Round(xy[i,df.nvars]);
+                    tmpi = 0;
+                    for(j=1; j<=df.nclasses-1; j++)
+                    {
+                        if( (double)(y[j])>(double)(y[tmpi]) )
+                        {
+                            tmpi = j;
+                        }
+                    }
+                    for(j=0; j<=df.nclasses-1; j++)
+                    {
+                        if( j==k )
+                        {
+                            result = result+math.sqr(y[j]-1);
+                        }
+                        else
+                        {
+                            result = result+math.sqr(y[j]);
+                        }
+                    }
+                }
+                else
+                {
+                    
+                    //
+                    // regression-specific code
+                    //
+                    result = result+math.sqr(y[0]-xy[i,df.nvars]);
+                }
+            }
+            result = Math.Sqrt(result/(npoints*df.nclasses));
+            return result;
+        }
+
+
+        /*************************************************************************
+        Average error on the test set
+
+        INPUT PARAMETERS:
+            DF      -   decision forest model
+            XY      -   test set
+            NPoints -   test set size
+
+        RESULT:
+            Its meaning for regression task is obvious. As for
+            classification task, it means average error when estimating posterior
+            probabilities.
+
+          -- ALGLIB --
+             Copyright 16.02.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static double dfavgerror(decisionforest df,
+            double[,] xy,
+            int npoints)
+        {
+            double result = 0;
+            double[] x = new double[0];
+            double[] y = new double[0];
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int i_ = 0;
+
+            x = new double[df.nvars-1+1];
+            y = new double[df.nclasses-1+1];
+            result = 0;
+            for(i=0; i<=npoints-1; i++)
+            {
+                for(i_=0; i_<=df.nvars-1;i_++)
+                {
+                    x[i_] = xy[i,i_];
+                }
+                dfprocess(df, x, ref y);
+                if( df.nclasses>1 )
+                {
+                    
+                    //
+                    // classification-specific code
+                    //
+                    k = (int)Math.Round(xy[i,df.nvars]);
+                    for(j=0; j<=df.nclasses-1; j++)
+                    {
+                        if( j==k )
+                        {
+                            result = result+Math.Abs(y[j]-1);
+                        }
+                        else
+                        {
+                            result = result+Math.Abs(y[j]);
+                        }
+                    }
+                }
+                else
+                {
+                    
+                    //
+                    // regression-specific code
+                    //
+                    result = result+Math.Abs(y[0]-xy[i,df.nvars]);
+                }
+            }
+            result = result/(npoints*df.nclasses);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Average relative error on the test set
+
+        INPUT PARAMETERS:
+            DF      -   decision forest model
+            XY      -   test set
+            NPoints -   test set size
+
+        RESULT:
+            Its meaning for regression task is obvious. As for
+            classification task, it means average relative error when estimating
+            posterior probability of belonging to the correct class.
+
+          -- ALGLIB --
+             Copyright 16.02.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static double dfavgrelerror(decisionforest df,
+            double[,] xy,
+            int npoints)
+        {
+            double result = 0;
+            double[] x = new double[0];
+            double[] y = new double[0];
+            int relcnt = 0;
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int i_ = 0;
+
+            x = new double[df.nvars-1+1];
+            y = new double[df.nclasses-1+1];
+            result = 0;
+            relcnt = 0;
+            for(i=0; i<=npoints-1; i++)
+            {
+                for(i_=0; i_<=df.nvars-1;i_++)
+                {
+                    x[i_] = xy[i,i_];
+                }
+                dfprocess(df, x, ref y);
+                if( df.nclasses>1 )
+                {
+                    
+                    //
+                    // classification-specific code
+                    //
+                    k = (int)Math.Round(xy[i,df.nvars]);
+                    for(j=0; j<=df.nclasses-1; j++)
+                    {
+                        if( j==k )
+                        {
+                            result = result+Math.Abs(y[j]-1);
+                            relcnt = relcnt+1;
+                        }
+                    }
+                }
+                else
+                {
+                    
+                    //
+                    // regression-specific code
+                    //
+                    if( (double)(xy[i,df.nvars])!=(double)(0) )
+                    {
+                        result = result+Math.Abs((y[0]-xy[i,df.nvars])/xy[i,df.nvars]);
+                        relcnt = relcnt+1;
+                    }
+                }
+            }
+            if( relcnt>0 )
+            {
+                result = result/relcnt;
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        Copying of DecisionForest strucure
+
+        INPUT PARAMETERS:
+            DF1 -   original
+
+        OUTPUT PARAMETERS:
+            DF2 -   copy
+
+          -- ALGLIB --
+             Copyright 13.02.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void dfcopy(decisionforest df1,
+            decisionforest df2)
+        {
+            int i_ = 0;
+
+            df2.nvars = df1.nvars;
+            df2.nclasses = df1.nclasses;
+            df2.ntrees = df1.ntrees;
+            df2.bufsize = df1.bufsize;
+            df2.trees = new double[df1.bufsize-1+1];
+            for(i_=0; i_<=df1.bufsize-1;i_++)
+            {
+                df2.trees[i_] = df1.trees[i_];
+            }
+        }
+
+
+        /*************************************************************************
+        Serializer: allocation
+
+          -- ALGLIB --
+             Copyright 14.03.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void dfalloc(alglib.serializer s,
+            decisionforest forest)
+        {
+            s.alloc_entry();
+            s.alloc_entry();
+            s.alloc_entry();
+            s.alloc_entry();
+            s.alloc_entry();
+            s.alloc_entry();
+            apserv.allocrealarray(s, forest.trees, forest.bufsize);
+        }
+
+
+        /*************************************************************************
+        Serializer: serialization
+
+          -- ALGLIB --
+             Copyright 14.03.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void dfserialize(alglib.serializer s,
+            decisionforest forest)
+        {
+            s.serialize_int(scodes.getrdfserializationcode());
+            s.serialize_int(dffirstversion);
+            s.serialize_int(forest.nvars);
+            s.serialize_int(forest.nclasses);
+            s.serialize_int(forest.ntrees);
+            s.serialize_int(forest.bufsize);
+            apserv.serializerealarray(s, forest.trees, forest.bufsize);
+        }
+
+
+        /*************************************************************************
+        Serializer: unserialization
+
+          -- ALGLIB --
+             Copyright 14.03.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void dfunserialize(alglib.serializer s,
+            decisionforest forest)
+        {
+            int i0 = 0;
+            int i1 = 0;
+
+            
+            //
+            // check correctness of header
+            //
+            i0 = s.unserialize_int();
+            alglib.ap.assert(i0==scodes.getrdfserializationcode(), "DFUnserialize: stream header corrupted");
+            i1 = s.unserialize_int();
+            alglib.ap.assert(i1==dffirstversion, "DFUnserialize: stream header corrupted");
+            
+            //
+            // Unserialize data
+            //
+            forest.nvars = s.unserialize_int();
+            forest.nclasses = s.unserialize_int();
+            forest.ntrees = s.unserialize_int();
+            forest.bufsize = s.unserialize_int();
+            apserv.unserializerealarray(s, ref forest.trees);
+        }
+
+
+        /*************************************************************************
+        Classification error
+        *************************************************************************/
+        private static int dfclserror(decisionforest df,
+            double[,] xy,
+            int npoints)
+        {
+            int result = 0;
+            double[] x = new double[0];
+            double[] y = new double[0];
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int tmpi = 0;
+            int i_ = 0;
+
+            if( df.nclasses<=1 )
+            {
+                result = 0;
+                return result;
+            }
+            x = new double[df.nvars-1+1];
+            y = new double[df.nclasses-1+1];
+            result = 0;
+            for(i=0; i<=npoints-1; i++)
+            {
+                for(i_=0; i_<=df.nvars-1;i_++)
+                {
+                    x[i_] = xy[i,i_];
+                }
+                dfprocess(df, x, ref y);
+                k = (int)Math.Round(xy[i,df.nvars]);
+                tmpi = 0;
+                for(j=1; j<=df.nclasses-1; j++)
+                {
+                    if( (double)(y[j])>(double)(y[tmpi]) )
+                    {
+                        tmpi = j;
+                    }
+                }
+                if( tmpi!=k )
+                {
+                    result = result+1;
+                }
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        Internal subroutine for processing one decision tree starting at Offs
+        *************************************************************************/
+        private static void dfprocessinternal(decisionforest df,
+            int offs,
+            double[] x,
+            ref double[] y)
+        {
+            int k = 0;
+            int idx = 0;
+
+            
+            //
+            // Set pointer to the root
+            //
+            k = offs+1;
+            
+            //
+            // Navigate through the tree
+            //
+            while( true )
+            {
+                if( (double)(df.trees[k])==(double)(-1) )
+                {
+                    if( df.nclasses==1 )
+                    {
+                        y[0] = y[0]+df.trees[k+1];
+                    }
+                    else
+                    {
+                        idx = (int)Math.Round(df.trees[k+1]);
+                        y[idx] = y[idx]+1;
+                    }
+                    break;
+                }
+                if( (double)(x[(int)Math.Round(df.trees[k])])<(double)(df.trees[k+1]) )
+                {
+                    k = k+innernodewidth;
+                }
+                else
+                {
+                    k = offs+(int)Math.Round(df.trees[k+2]);
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Builds one decision tree. Just a wrapper for the DFBuildTreeRec.
+        *************************************************************************/
+        private static void dfbuildtree(double[,] xy,
+            int npoints,
+            int nvars,
+            int nclasses,
+            int nfeatures,
+            int nvarsinpool,
+            int flags,
+            dfinternalbuffers bufs)
+        {
+            int numprocessed = 0;
+            int i = 0;
+
+            alglib.ap.assert(npoints>0);
+            
+            //
+            // Prepare IdxBuf. It stores indices of the training set elements.
+            // When training set is being split, contents of IdxBuf is
+            // correspondingly reordered so we can know which elements belong
+            // to which branch of decision tree.
+            //
+            for(i=0; i<=npoints-1; i++)
+            {
+                bufs.idxbuf[i] = i;
+            }
+            
+            //
+            // Recursive procedure
+            //
+            numprocessed = 1;
+            dfbuildtreerec(xy, npoints, nvars, nclasses, nfeatures, nvarsinpool, flags, ref numprocessed, 0, npoints-1, bufs);
+            bufs.treebuf[0] = numprocessed;
+        }
+
+
+        /*************************************************************************
+        Builds one decision tree (internal recursive subroutine)
+
+        Parameters:
+            TreeBuf     -   large enough array, at least TreeSize
+            IdxBuf      -   at least NPoints elements
+            TmpBufR     -   at least NPoints
+            TmpBufR2    -   at least NPoints
+            TmpBufI     -   at least NPoints
+            TmpBufI2    -   at least NPoints+1
+        *************************************************************************/
+        private static void dfbuildtreerec(double[,] xy,
+            int npoints,
+            int nvars,
+            int nclasses,
+            int nfeatures,
+            int nvarsinpool,
+            int flags,
+            ref int numprocessed,
+            int idx1,
+            int idx2,
+            dfinternalbuffers bufs)
+        {
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            bool bflag = new bool();
+            int i1 = 0;
+            int i2 = 0;
+            int info = 0;
+            double sl = 0;
+            double sr = 0;
+            double w = 0;
+            int idxbest = 0;
+            double ebest = 0;
+            double tbest = 0;
+            int varcur = 0;
+            double s = 0;
+            double v = 0;
+            double v1 = 0;
+            double v2 = 0;
+            double threshold = 0;
+            int oldnp = 0;
+            double currms = 0;
+            bool useevs = new bool();
+
+            
+            //
+            // these initializers are not really necessary,
+            // but without them compiler complains about uninitialized locals
+            //
+            tbest = 0;
+            
+            //
+            // Prepare
+            //
+            alglib.ap.assert(npoints>0);
+            alglib.ap.assert(idx2>=idx1);
+            useevs = flags/dfuseevs%2!=0;
+            
+            //
+            // Leaf node
+            //
+            if( idx2==idx1 )
+            {
+                bufs.treebuf[numprocessed] = -1;
+                bufs.treebuf[numprocessed+1] = xy[bufs.idxbuf[idx1],nvars];
+                numprocessed = numprocessed+leafnodewidth;
+                return;
+            }
+            
+            //
+            // Non-leaf node.
+            // Select random variable, prepare split:
+            // 1. prepare default solution - no splitting, class at random
+            // 2. investigate possible splits, compare with default/best
+            //
+            idxbest = -1;
+            if( nclasses>1 )
+            {
+                
+                //
+                // default solution for classification
+                //
+                for(i=0; i<=nclasses-1; i++)
+                {
+                    bufs.classibuf[i] = 0;
+                }
+                s = idx2-idx1+1;
+                for(i=idx1; i<=idx2; i++)
+                {
+                    j = (int)Math.Round(xy[bufs.idxbuf[i],nvars]);
+                    bufs.classibuf[j] = bufs.classibuf[j]+1;
+                }
+                ebest = 0;
+                for(i=0; i<=nclasses-1; i++)
+                {
+                    ebest = ebest+bufs.classibuf[i]*math.sqr(1-bufs.classibuf[i]/s)+(s-bufs.classibuf[i])*math.sqr(bufs.classibuf[i]/s);
+                }
+                ebest = Math.Sqrt(ebest/(nclasses*(idx2-idx1+1)));
+            }
+            else
+            {
+                
+                //
+                // default solution for regression
+                //
+                v = 0;
+                for(i=idx1; i<=idx2; i++)
+                {
+                    v = v+xy[bufs.idxbuf[i],nvars];
+                }
+                v = v/(idx2-idx1+1);
+                ebest = 0;
+                for(i=idx1; i<=idx2; i++)
+                {
+                    ebest = ebest+math.sqr(xy[bufs.idxbuf[i],nvars]-v);
+                }
+                ebest = Math.Sqrt(ebest/(idx2-idx1+1));
+            }
+            i = 0;
+            while( i<=Math.Min(nfeatures, nvarsinpool)-1 )
+            {
+                
+                //
+                // select variables from pool
+                //
+                j = i+math.randominteger(nvarsinpool-i);
+                k = bufs.varpool[i];
+                bufs.varpool[i] = bufs.varpool[j];
+                bufs.varpool[j] = k;
+                varcur = bufs.varpool[i];
+                
+                //
+                // load variable values to working array
+                //
+                // apply EVS preprocessing: if all variable values are same,
+                // variable is excluded from pool.
+                //
+                // This is necessary for binary pre-splits (see later) to work.
+                //
+                for(j=idx1; j<=idx2; j++)
+                {
+                    bufs.tmpbufr[j-idx1] = xy[bufs.idxbuf[j],varcur];
+                }
+                if( useevs )
+                {
+                    bflag = false;
+                    v = bufs.tmpbufr[0];
+                    for(j=0; j<=idx2-idx1; j++)
+                    {
+                        if( (double)(bufs.tmpbufr[j])!=(double)(v) )
+                        {
+                            bflag = true;
+                            break;
+                        }
+                    }
+                    if( !bflag )
+                    {
+                        
+                        //
+                        // exclude variable from pool,
+                        // go to the next iteration.
+                        // I is not increased.
+                        //
+                        k = bufs.varpool[i];
+                        bufs.varpool[i] = bufs.varpool[nvarsinpool-1];
+                        bufs.varpool[nvarsinpool-1] = k;
+                        nvarsinpool = nvarsinpool-1;
+                        continue;
+                    }
+                }
+                
+                //
+                // load labels to working array
+                //
+                if( nclasses>1 )
+                {
+                    for(j=idx1; j<=idx2; j++)
+                    {
+                        bufs.tmpbufi[j-idx1] = (int)Math.Round(xy[bufs.idxbuf[j],nvars]);
+                    }
+                }
+                else
+                {
+                    for(j=idx1; j<=idx2; j++)
+                    {
+                        bufs.tmpbufr2[j-idx1] = xy[bufs.idxbuf[j],nvars];
+                    }
+                }
+                
+                //
+                // calculate split
+                //
+                if( useevs && bufs.evsbin[varcur] )
+                {
+                    
+                    //
+                    // Pre-calculated splits for binary variables.
+                    // Threshold is already known, just calculate RMS error
+                    //
+                    threshold = bufs.evssplits[varcur];
+                    if( nclasses>1 )
+                    {
+                        
+                        //
+                        // classification-specific code
+                        //
+                        for(j=0; j<=2*nclasses-1; j++)
+                        {
+                            bufs.classibuf[j] = 0;
+                        }
+                        sl = 0;
+                        sr = 0;
+                        for(j=0; j<=idx2-idx1; j++)
+                        {
+                            k = bufs.tmpbufi[j];
+                            if( (double)(bufs.tmpbufr[j])<(double)(threshold) )
+                            {
+                                bufs.classibuf[k] = bufs.classibuf[k]+1;
+                                sl = sl+1;
+                            }
+                            else
+                            {
+                                bufs.classibuf[k+nclasses] = bufs.classibuf[k+nclasses]+1;
+                                sr = sr+1;
+                            }
+                        }
+                        alglib.ap.assert((double)(sl)!=(double)(0) && (double)(sr)!=(double)(0), "DFBuildTreeRec: something strange!");
+                        currms = 0;
+                        for(j=0; j<=nclasses-1; j++)
+                        {
+                            w = bufs.classibuf[j];
+                            currms = currms+w*math.sqr(w/sl-1);
+                            currms = currms+(sl-w)*math.sqr(w/sl);
+                            w = bufs.classibuf[nclasses+j];
+                            currms = currms+w*math.sqr(w/sr-1);
+                            currms = currms+(sr-w)*math.sqr(w/sr);
+                        }
+                        currms = Math.Sqrt(currms/(nclasses*(idx2-idx1+1)));
+                    }
+                    else
+                    {
+                        
+                        //
+                        // regression-specific code
+                        //
+                        sl = 0;
+                        sr = 0;
+                        v1 = 0;
+                        v2 = 0;
+                        for(j=0; j<=idx2-idx1; j++)
+                        {
+                            if( (double)(bufs.tmpbufr[j])<(double)(threshold) )
+                            {
+                                v1 = v1+bufs.tmpbufr2[j];
+                                sl = sl+1;
+                            }
+                            else
+                            {
+                                v2 = v2+bufs.tmpbufr2[j];
+                                sr = sr+1;
+                            }
+                        }
+                        alglib.ap.assert((double)(sl)!=(double)(0) && (double)(sr)!=(double)(0), "DFBuildTreeRec: something strange!");
+                        v1 = v1/sl;
+                        v2 = v2/sr;
+                        currms = 0;
+                        for(j=0; j<=idx2-idx1; j++)
+                        {
+                            if( (double)(bufs.tmpbufr[j])<(double)(threshold) )
+                            {
+                                currms = currms+math.sqr(v1-bufs.tmpbufr2[j]);
+                            }
+                            else
+                            {
+                                currms = currms+math.sqr(v2-bufs.tmpbufr2[j]);
+                            }
+                        }
+                        currms = Math.Sqrt(currms/(idx2-idx1+1));
+                    }
+                    info = 1;
+                }
+                else
+                {
+                    
+                    //
+                    // Generic splits
+                    //
+                    if( nclasses>1 )
+                    {
+                        dfsplitc(ref bufs.tmpbufr, ref bufs.tmpbufi, ref bufs.classibuf, idx2-idx1+1, nclasses, dfusestrongsplits, ref info, ref threshold, ref currms, ref bufs.sortrbuf, ref bufs.sortibuf);
+                    }
+                    else
+                    {
+                        dfsplitr(ref bufs.tmpbufr, ref bufs.tmpbufr2, idx2-idx1+1, dfusestrongsplits, ref info, ref threshold, ref currms, ref bufs.sortrbuf, ref bufs.sortrbuf2);
+                    }
+                }
+                if( info>0 )
+                {
+                    if( (double)(currms)<=(double)(ebest) )
+                    {
+                        ebest = currms;
+                        idxbest = varcur;
+                        tbest = threshold;
+                    }
+                }
+                
+                //
+                // Next iteration
+                //
+                i = i+1;
+            }
+            
+            //
+            // to split or not to split
+            //
+            if( idxbest<0 )
+            {
+                
+                //
+                // All values are same, cannot split.
+                //
+                bufs.treebuf[numprocessed] = -1;
+                if( nclasses>1 )
+                {
+                    
+                    //
+                    // Select random class label (randomness allows us to
+                    // approximate distribution of the classes)
+                    //
+                    bufs.treebuf[numprocessed+1] = (int)Math.Round(xy[bufs.idxbuf[idx1+math.randominteger(idx2-idx1+1)],nvars]);
+                }
+                else
+                {
+                    
+                    //
+                    // Select average (for regression task).
+                    //
+                    v = 0;
+                    for(i=idx1; i<=idx2; i++)
+                    {
+                        v = v+xy[bufs.idxbuf[i],nvars]/(idx2-idx1+1);
+                    }
+                    bufs.treebuf[numprocessed+1] = v;
+                }
+                numprocessed = numprocessed+leafnodewidth;
+            }
+            else
+            {
+                
+                //
+                // we can split
+                //
+                bufs.treebuf[numprocessed] = idxbest;
+                bufs.treebuf[numprocessed+1] = tbest;
+                i1 = idx1;
+                i2 = idx2;
+                while( i1<=i2 )
+                {
+                    
+                    //
+                    // Reorder indices so that left partition is in [Idx1..I1-1],
+                    // and right partition is in [I2+1..Idx2]
+                    //
+                    if( (double)(xy[bufs.idxbuf[i1],idxbest])<(double)(tbest) )
+                    {
+                        i1 = i1+1;
+                        continue;
+                    }
+                    if( (double)(xy[bufs.idxbuf[i2],idxbest])>=(double)(tbest) )
+                    {
+                        i2 = i2-1;
+                        continue;
+                    }
+                    j = bufs.idxbuf[i1];
+                    bufs.idxbuf[i1] = bufs.idxbuf[i2];
+                    bufs.idxbuf[i2] = j;
+                    i1 = i1+1;
+                    i2 = i2-1;
+                }
+                oldnp = numprocessed;
+                numprocessed = numprocessed+innernodewidth;
+                dfbuildtreerec(xy, npoints, nvars, nclasses, nfeatures, nvarsinpool, flags, ref numprocessed, idx1, i1-1, bufs);
+                bufs.treebuf[oldnp+2] = numprocessed;
+                dfbuildtreerec(xy, npoints, nvars, nclasses, nfeatures, nvarsinpool, flags, ref numprocessed, i2+1, idx2, bufs);
+            }
+        }
+
+
+        /*************************************************************************
+        Makes split on attribute
+        *************************************************************************/
+        private static void dfsplitc(ref double[] x,
+            ref int[] c,
+            ref int[] cntbuf,
+            int n,
+            int nc,
+            int flags,
+            ref int info,
+            ref double threshold,
+            ref double e,
+            ref double[] sortrbuf,
+            ref int[] sortibuf)
+        {
+            int i = 0;
+            int neq = 0;
+            int nless = 0;
+            int ngreater = 0;
+            int q = 0;
+            int qmin = 0;
+            int qmax = 0;
+            int qcnt = 0;
+            double cursplit = 0;
+            int nleft = 0;
+            double v = 0;
+            double cure = 0;
+            double w = 0;
+            double sl = 0;
+            double sr = 0;
+
+            info = 0;
+            threshold = 0;
+            e = 0;
+
+            tsort.tagsortfasti(ref x, ref c, ref sortrbuf, ref sortibuf, n);
+            e = math.maxrealnumber;
+            threshold = 0.5*(x[0]+x[n-1]);
+            info = -3;
+            if( flags/dfusestrongsplits%2==0 )
+            {
+                
+                //
+                // weak splits, split at half
+                //
+                qcnt = 2;
+                qmin = 1;
+                qmax = 1;
+            }
+            else
+            {
+                
+                //
+                // strong splits: choose best quartile
+                //
+                qcnt = 4;
+                qmin = 1;
+                qmax = 3;
+            }
+            for(q=qmin; q<=qmax; q++)
+            {
+                cursplit = x[n*q/qcnt];
+                neq = 0;
+                nless = 0;
+                ngreater = 0;
+                for(i=0; i<=n-1; i++)
+                {
+                    if( (double)(x[i])<(double)(cursplit) )
+                    {
+                        nless = nless+1;
+                    }
+                    if( (double)(x[i])==(double)(cursplit) )
+                    {
+                        neq = neq+1;
+                    }
+                    if( (double)(x[i])>(double)(cursplit) )
+                    {
+                        ngreater = ngreater+1;
+                    }
+                }
+                alglib.ap.assert(neq!=0, "DFSplitR: NEq=0, something strange!!!");
+                if( nless!=0 || ngreater!=0 )
+                {
+                    
+                    //
+                    // set threshold between two partitions, with
+                    // some tweaking to avoid problems with floating point
+                    // arithmetics.
+                    //
+                    // The problem is that when you calculates C = 0.5*(A+B) there
+                    // can be no C which lies strictly between A and B (for example,
+                    // there is no floating point number which is
+                    // greater than 1 and less than 1+eps). In such situations
+                    // we choose right side as theshold (remember that
+                    // points which lie on threshold falls to the right side).
+                    //
+                    if( nless<ngreater )
+                    {
+                        cursplit = 0.5*(x[nless+neq-1]+x[nless+neq]);
+                        nleft = nless+neq;
+                        if( (double)(cursplit)<=(double)(x[nless+neq-1]) )
+                        {
+                            cursplit = x[nless+neq];
+                        }
+                    }
+                    else
+                    {
+                        cursplit = 0.5*(x[nless-1]+x[nless]);
+                        nleft = nless;
+                        if( (double)(cursplit)<=(double)(x[nless-1]) )
+                        {
+                            cursplit = x[nless];
+                        }
+                    }
+                    info = 1;
+                    cure = 0;
+                    for(i=0; i<=2*nc-1; i++)
+                    {
+                        cntbuf[i] = 0;
+                    }
+                    for(i=0; i<=nleft-1; i++)
+                    {
+                        cntbuf[c[i]] = cntbuf[c[i]]+1;
+                    }
+                    for(i=nleft; i<=n-1; i++)
+                    {
+                        cntbuf[nc+c[i]] = cntbuf[nc+c[i]]+1;
+                    }
+                    sl = nleft;
+                    sr = n-nleft;
+                    v = 0;
+                    for(i=0; i<=nc-1; i++)
+                    {
+                        w = cntbuf[i];
+                        v = v+w*math.sqr(w/sl-1);
+                        v = v+(sl-w)*math.sqr(w/sl);
+                        w = cntbuf[nc+i];
+                        v = v+w*math.sqr(w/sr-1);
+                        v = v+(sr-w)*math.sqr(w/sr);
+                    }
+                    cure = Math.Sqrt(v/(nc*n));
+                    if( (double)(cure)<(double)(e) )
+                    {
+                        threshold = cursplit;
+                        e = cure;
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Makes split on attribute
+        *************************************************************************/
+        private static void dfsplitr(ref double[] x,
+            ref double[] y,
+            int n,
+            int flags,
+            ref int info,
+            ref double threshold,
+            ref double e,
+            ref double[] sortrbuf,
+            ref double[] sortrbuf2)
+        {
+            int i = 0;
+            int neq = 0;
+            int nless = 0;
+            int ngreater = 0;
+            int q = 0;
+            int qmin = 0;
+            int qmax = 0;
+            int qcnt = 0;
+            double cursplit = 0;
+            int nleft = 0;
+            double v = 0;
+            double cure = 0;
+
+            info = 0;
+            threshold = 0;
+            e = 0;
+
+            tsort.tagsortfastr(ref x, ref y, ref sortrbuf, ref sortrbuf2, n);
+            e = math.maxrealnumber;
+            threshold = 0.5*(x[0]+x[n-1]);
+            info = -3;
+            if( flags/dfusestrongsplits%2==0 )
+            {
+                
+                //
+                // weak splits, split at half
+                //
+                qcnt = 2;
+                qmin = 1;
+                qmax = 1;
+            }
+            else
+            {
+                
+                //
+                // strong splits: choose best quartile
+                //
+                qcnt = 4;
+                qmin = 1;
+                qmax = 3;
+            }
+            for(q=qmin; q<=qmax; q++)
+            {
+                cursplit = x[n*q/qcnt];
+                neq = 0;
+                nless = 0;
+                ngreater = 0;
+                for(i=0; i<=n-1; i++)
+                {
+                    if( (double)(x[i])<(double)(cursplit) )
+                    {
+                        nless = nless+1;
+                    }
+                    if( (double)(x[i])==(double)(cursplit) )
+                    {
+                        neq = neq+1;
+                    }
+                    if( (double)(x[i])>(double)(cursplit) )
+                    {
+                        ngreater = ngreater+1;
+                    }
+                }
+                alglib.ap.assert(neq!=0, "DFSplitR: NEq=0, something strange!!!");
+                if( nless!=0 || ngreater!=0 )
+                {
+                    
+                    //
+                    // set threshold between two partitions, with
+                    // some tweaking to avoid problems with floating point
+                    // arithmetics.
+                    //
+                    // The problem is that when you calculates C = 0.5*(A+B) there
+                    // can be no C which lies strictly between A and B (for example,
+                    // there is no floating point number which is
+                    // greater than 1 and less than 1+eps). In such situations
+                    // we choose right side as theshold (remember that
+                    // points which lie on threshold falls to the right side).
+                    //
+                    if( nless<ngreater )
+                    {
+                        cursplit = 0.5*(x[nless+neq-1]+x[nless+neq]);
+                        nleft = nless+neq;
+                        if( (double)(cursplit)<=(double)(x[nless+neq-1]) )
+                        {
+                            cursplit = x[nless+neq];
+                        }
+                    }
+                    else
+                    {
+                        cursplit = 0.5*(x[nless-1]+x[nless]);
+                        nleft = nless;
+                        if( (double)(cursplit)<=(double)(x[nless-1]) )
+                        {
+                            cursplit = x[nless];
+                        }
+                    }
+                    info = 1;
+                    cure = 0;
+                    v = 0;
+                    for(i=0; i<=nleft-1; i++)
+                    {
+                        v = v+y[i];
+                    }
+                    v = v/nleft;
+                    for(i=0; i<=nleft-1; i++)
+                    {
+                        cure = cure+math.sqr(y[i]-v);
+                    }
+                    v = 0;
+                    for(i=nleft; i<=n-1; i++)
+                    {
+                        v = v+y[i];
+                    }
+                    v = v/(n-nleft);
+                    for(i=nleft; i<=n-1; i++)
+                    {
+                        cure = cure+math.sqr(y[i]-v);
+                    }
+                    cure = Math.Sqrt(cure/n);
+                    if( (double)(cure)<(double)(e) )
+                    {
+                        threshold = cursplit;
+                        e = cure;
+                    }
+                }
+            }
+        }
+
+
+    }
+    public class linreg
+    {
+        public class linearmodel
+        {
+            public double[] w;
+            public linearmodel()
+            {
+                w = new double[0];
+            }
+        };
+
+
+        /*************************************************************************
+        LRReport structure contains additional information about linear model:
+        * C             -   covariation matrix,  array[0..NVars,0..NVars].
+                            C[i,j] = Cov(A[i],A[j])
+        * RMSError      -   root mean square error on a training set
+        * AvgError      -   average error on a training set
+        * AvgRelError   -   average relative error on a training set (excluding
+                            observations with zero function value).
+        * CVRMSError    -   leave-one-out cross-validation estimate of
+                            generalization error. Calculated using fast algorithm
+                            with O(NVars*NPoints) complexity.
+        * CVAvgError    -   cross-validation estimate of average error
+        * CVAvgRelError -   cross-validation estimate of average relative error
+
+        All other fields of the structure are intended for internal use and should
+        not be used outside ALGLIB.
+        *************************************************************************/
+        public class lrreport
+        {
+            public double[,] c;
+            public double rmserror;
+            public double avgerror;
+            public double avgrelerror;
+            public double cvrmserror;
+            public double cvavgerror;
+            public double cvavgrelerror;
+            public int ncvdefects;
+            public int[] cvdefects;
+            public lrreport()
+            {
+                c = new double[0,0];
+                cvdefects = new int[0];
+            }
+        };
+
+
+
+
+        public const int lrvnum = 5;
+
+
+        /*************************************************************************
+        Linear regression
+
+        Subroutine builds model:
+
+            Y = A(0)*X[0] + ... + A(N-1)*X[N-1] + A(N)
+
+        and model found in ALGLIB format, covariation matrix, training set  errors
+        (rms,  average,  average  relative)   and  leave-one-out  cross-validation
+        estimate of the generalization error. CV  estimate calculated  using  fast
+        algorithm with O(NPoints*NVars) complexity.
+
+        When  covariation  matrix  is  calculated  standard deviations of function
+        values are assumed to be equal to RMS error on the training set.
+
+        INPUT PARAMETERS:
+            XY          -   training set, array [0..NPoints-1,0..NVars]:
+                            * NVars columns - independent variables
+                            * last column - dependent variable
+            NPoints     -   training set size, NPoints>NVars+1
+            NVars       -   number of independent variables
+
+        OUTPUT PARAMETERS:
+            Info        -   return code:
+                            * -255, in case of unknown internal error
+                            * -4, if internal SVD subroutine haven't converged
+                            * -1, if incorrect parameters was passed (NPoints<NVars+2, NVars<1).
+                            *  1, if subroutine successfully finished
+            LM          -   linear model in the ALGLIB format. Use subroutines of
+                            this unit to work with the model.
+            AR          -   additional results
+
+
+          -- ALGLIB --
+             Copyright 02.08.2008 by Bochkanov Sergey
+        *************************************************************************/
+        public static void lrbuild(double[,] xy,
+            int npoints,
+            int nvars,
+            ref int info,
+            linearmodel lm,
+            lrreport ar)
+        {
+            double[] s = new double[0];
+            int i = 0;
+            double sigma2 = 0;
+            int i_ = 0;
+
+            info = 0;
+
+            if( npoints<=nvars+1 || nvars<1 )
+            {
+                info = -1;
+                return;
+            }
+            s = new double[npoints-1+1];
+            for(i=0; i<=npoints-1; i++)
+            {
+                s[i] = 1;
+            }
+            lrbuilds(xy, s, npoints, nvars, ref info, lm, ar);
+            if( info<0 )
+            {
+                return;
+            }
+            sigma2 = math.sqr(ar.rmserror)*npoints/(npoints-nvars-1);
+            for(i=0; i<=nvars; i++)
+            {
+                for(i_=0; i_<=nvars;i_++)
+                {
+                    ar.c[i,i_] = sigma2*ar.c[i,i_];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Linear regression
+
+        Variant of LRBuild which uses vector of standatd deviations (errors in
+        function values).
+
+        INPUT PARAMETERS:
+            XY          -   training set, array [0..NPoints-1,0..NVars]:
+                            * NVars columns - independent variables
+                            * last column - dependent variable
+            S           -   standard deviations (errors in function values)
+                            array[0..NPoints-1], S[i]>0.
+            NPoints     -   training set size, NPoints>NVars+1
+            NVars       -   number of independent variables
+
+        OUTPUT PARAMETERS:
+            Info        -   return code:
+                            * -255, in case of unknown internal error
+                            * -4, if internal SVD subroutine haven't converged
+                            * -1, if incorrect parameters was passed (NPoints<NVars+2, NVars<1).
+                            * -2, if S[I]<=0
+                            *  1, if subroutine successfully finished
+            LM          -   linear model in the ALGLIB format. Use subroutines of
+                            this unit to work with the model.
+            AR          -   additional results
+
+
+          -- ALGLIB --
+             Copyright 02.08.2008 by Bochkanov Sergey
+        *************************************************************************/
+        public static void lrbuilds(double[,] xy,
+            double[] s,
+            int npoints,
+            int nvars,
+            ref int info,
+            linearmodel lm,
+            lrreport ar)
+        {
+            double[,] xyi = new double[0,0];
+            double[] x = new double[0];
+            double[] means = new double[0];
+            double[] sigmas = new double[0];
+            int i = 0;
+            int j = 0;
+            double v = 0;
+            int offs = 0;
+            double mean = 0;
+            double variance = 0;
+            double skewness = 0;
+            double kurtosis = 0;
+            int i_ = 0;
+
+            info = 0;
+
+            
+            //
+            // Test parameters
+            //
+            if( npoints<=nvars+1 || nvars<1 )
+            {
+                info = -1;
+                return;
+            }
+            
+            //
+            // Copy data, add one more column (constant term)
+            //
+            xyi = new double[npoints-1+1, nvars+1+1];
+            for(i=0; i<=npoints-1; i++)
+            {
+                for(i_=0; i_<=nvars-1;i_++)
+                {
+                    xyi[i,i_] = xy[i,i_];
+                }
+                xyi[i,nvars] = 1;
+                xyi[i,nvars+1] = xy[i,nvars];
+            }
+            
+            //
+            // Standartization
+            //
+            x = new double[npoints-1+1];
+            means = new double[nvars-1+1];
+            sigmas = new double[nvars-1+1];
+            for(j=0; j<=nvars-1; j++)
+            {
+                for(i_=0; i_<=npoints-1;i_++)
+                {
+                    x[i_] = xy[i_,j];
+                }
+                basestat.samplemoments(x, npoints, ref mean, ref variance, ref skewness, ref kurtosis);
+                means[j] = mean;
+                sigmas[j] = Math.Sqrt(variance);
+                if( (double)(sigmas[j])==(double)(0) )
+                {
+                    sigmas[j] = 1;
+                }
+                for(i=0; i<=npoints-1; i++)
+                {
+                    xyi[i,j] = (xyi[i,j]-means[j])/sigmas[j];
+                }
+            }
+            
+            //
+            // Internal processing
+            //
+            lrinternal(xyi, s, npoints, nvars+1, ref info, lm, ar);
+            if( info<0 )
+            {
+                return;
+            }
+            
+            //
+            // Un-standartization
+            //
+            offs = (int)Math.Round(lm.w[3]);
+            for(j=0; j<=nvars-1; j++)
+            {
+                
+                //
+                // Constant term is updated (and its covariance too,
+                // since it gets some variance from J-th component)
+                //
+                lm.w[offs+nvars] = lm.w[offs+nvars]-lm.w[offs+j]*means[j]/sigmas[j];
+                v = means[j]/sigmas[j];
+                for(i_=0; i_<=nvars;i_++)
+                {
+                    ar.c[nvars,i_] = ar.c[nvars,i_] - v*ar.c[j,i_];
+                }
+                for(i_=0; i_<=nvars;i_++)
+                {
+                    ar.c[i_,nvars] = ar.c[i_,nvars] - v*ar.c[i_,j];
+                }
+                
+                //
+                // J-th term is updated
+                //
+                lm.w[offs+j] = lm.w[offs+j]/sigmas[j];
+                v = 1/sigmas[j];
+                for(i_=0; i_<=nvars;i_++)
+                {
+                    ar.c[j,i_] = v*ar.c[j,i_];
+                }
+                for(i_=0; i_<=nvars;i_++)
+                {
+                    ar.c[i_,j] = v*ar.c[i_,j];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Like LRBuildS, but builds model
+
+            Y = A(0)*X[0] + ... + A(N-1)*X[N-1]
+
+        i.e. with zero constant term.
+
+          -- ALGLIB --
+             Copyright 30.10.2008 by Bochkanov Sergey
+        *************************************************************************/
+        public static void lrbuildzs(double[,] xy,
+            double[] s,
+            int npoints,
+            int nvars,
+            ref int info,
+            linearmodel lm,
+            lrreport ar)
+        {
+            double[,] xyi = new double[0,0];
+            double[] x = new double[0];
+            double[] c = new double[0];
+            int i = 0;
+            int j = 0;
+            double v = 0;
+            int offs = 0;
+            double mean = 0;
+            double variance = 0;
+            double skewness = 0;
+            double kurtosis = 0;
+            int i_ = 0;
+
+            info = 0;
+
+            
+            //
+            // Test parameters
+            //
+            if( npoints<=nvars+1 || nvars<1 )
+            {
+                info = -1;
+                return;
+            }
+            
+            //
+            // Copy data, add one more column (constant term)
+            //
+            xyi = new double[npoints-1+1, nvars+1+1];
+            for(i=0; i<=npoints-1; i++)
+            {
+                for(i_=0; i_<=nvars-1;i_++)
+                {
+                    xyi[i,i_] = xy[i,i_];
+                }
+                xyi[i,nvars] = 0;
+                xyi[i,nvars+1] = xy[i,nvars];
+            }
+            
+            //
+            // Standartization: unusual scaling
+            //
+            x = new double[npoints-1+1];
+            c = new double[nvars-1+1];
+            for(j=0; j<=nvars-1; j++)
+            {
+                for(i_=0; i_<=npoints-1;i_++)
+                {
+                    x[i_] = xy[i_,j];
+                }
+                basestat.samplemoments(x, npoints, ref mean, ref variance, ref skewness, ref kurtosis);
+                if( (double)(Math.Abs(mean))>(double)(Math.Sqrt(variance)) )
+                {
+                    
+                    //
+                    // variation is relatively small, it is better to
+                    // bring mean value to 1
+                    //
+                    c[j] = mean;
+                }
+                else
+                {
+                    
+                    //
+                    // variation is large, it is better to bring variance to 1
+                    //
+                    if( (double)(variance)==(double)(0) )
+                    {
+                        variance = 1;
+                    }
+                    c[j] = Math.Sqrt(variance);
+                }
+                for(i=0; i<=npoints-1; i++)
+                {
+                    xyi[i,j] = xyi[i,j]/c[j];
+                }
+            }
+            
+            //
+            // Internal processing
+            //
+            lrinternal(xyi, s, npoints, nvars+1, ref info, lm, ar);
+            if( info<0 )
+            {
+                return;
+            }
+            
+            //
+            // Un-standartization
+            //
+            offs = (int)Math.Round(lm.w[3]);
+            for(j=0; j<=nvars-1; j++)
+            {
+                
+                //
+                // J-th term is updated
+                //
+                lm.w[offs+j] = lm.w[offs+j]/c[j];
+                v = 1/c[j];
+                for(i_=0; i_<=nvars;i_++)
+                {
+                    ar.c[j,i_] = v*ar.c[j,i_];
+                }
+                for(i_=0; i_<=nvars;i_++)
+                {
+                    ar.c[i_,j] = v*ar.c[i_,j];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Like LRBuild but builds model
+
+            Y = A(0)*X[0] + ... + A(N-1)*X[N-1]
+
+        i.e. with zero constant term.
+
+          -- ALGLIB --
+             Copyright 30.10.2008 by Bochkanov Sergey
+        *************************************************************************/
+        public static void lrbuildz(double[,] xy,
+            int npoints,
+            int nvars,
+            ref int info,
+            linearmodel lm,
+            lrreport ar)
+        {
+            double[] s = new double[0];
+            int i = 0;
+            double sigma2 = 0;
+            int i_ = 0;
+
+            info = 0;
+
+            if( npoints<=nvars+1 || nvars<1 )
+            {
+                info = -1;
+                return;
+            }
+            s = new double[npoints-1+1];
+            for(i=0; i<=npoints-1; i++)
+            {
+                s[i] = 1;
+            }
+            lrbuildzs(xy, s, npoints, nvars, ref info, lm, ar);
+            if( info<0 )
+            {
+                return;
+            }
+            sigma2 = math.sqr(ar.rmserror)*npoints/(npoints-nvars-1);
+            for(i=0; i<=nvars; i++)
+            {
+                for(i_=0; i_<=nvars;i_++)
+                {
+                    ar.c[i,i_] = sigma2*ar.c[i,i_];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Unpacks coefficients of linear model.
+
+        INPUT PARAMETERS:
+            LM          -   linear model in ALGLIB format
+
+        OUTPUT PARAMETERS:
+            V           -   coefficients, array[0..NVars]
+                            constant term (intercept) is stored in the V[NVars].
+            NVars       -   number of independent variables (one less than number
+                            of coefficients)
+
+          -- ALGLIB --
+             Copyright 30.08.2008 by Bochkanov Sergey
+        *************************************************************************/
+        public static void lrunpack(linearmodel lm,
+            ref double[] v,
+            ref int nvars)
+        {
+            int offs = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            v = new double[0];
+            nvars = 0;
+
+            alglib.ap.assert((int)Math.Round(lm.w[1])==lrvnum, "LINREG: Incorrect LINREG version!");
+            nvars = (int)Math.Round(lm.w[2]);
+            offs = (int)Math.Round(lm.w[3]);
+            v = new double[nvars+1];
+            i1_ = (offs) - (0);
+            for(i_=0; i_<=nvars;i_++)
+            {
+                v[i_] = lm.w[i_+i1_];
+            }
+        }
+
+
+        /*************************************************************************
+        "Packs" coefficients and creates linear model in ALGLIB format (LRUnpack
+        reversed).
+
+        INPUT PARAMETERS:
+            V           -   coefficients, array[0..NVars]
+            NVars       -   number of independent variables
+
+        OUTPUT PAREMETERS:
+            LM          -   linear model.
+
+          -- ALGLIB --
+             Copyright 30.08.2008 by Bochkanov Sergey
+        *************************************************************************/
+        public static void lrpack(double[] v,
+            int nvars,
+            linearmodel lm)
+        {
+            int offs = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            lm.w = new double[4+nvars+1];
+            offs = 4;
+            lm.w[0] = 4+nvars+1;
+            lm.w[1] = lrvnum;
+            lm.w[2] = nvars;
+            lm.w[3] = offs;
+            i1_ = (0) - (offs);
+            for(i_=offs; i_<=offs+nvars;i_++)
+            {
+                lm.w[i_] = v[i_+i1_];
+            }
+        }
+
+
+        /*************************************************************************
+        Procesing
+
+        INPUT PARAMETERS:
+            LM      -   linear model
+            X       -   input vector,  array[0..NVars-1].
+
+        Result:
+            value of linear model regression estimate
+
+          -- ALGLIB --
+             Copyright 03.09.2008 by Bochkanov Sergey
+        *************************************************************************/
+        public static double lrprocess(linearmodel lm,
+            double[] x)
+        {
+            double result = 0;
+            double v = 0;
+            int offs = 0;
+            int nvars = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            alglib.ap.assert((int)Math.Round(lm.w[1])==lrvnum, "LINREG: Incorrect LINREG version!");
+            nvars = (int)Math.Round(lm.w[2]);
+            offs = (int)Math.Round(lm.w[3]);
+            i1_ = (offs)-(0);
+            v = 0.0;
+            for(i_=0; i_<=nvars-1;i_++)
+            {
+                v += x[i_]*lm.w[i_+i1_];
+            }
+            result = v+lm.w[offs+nvars];
+            return result;
+        }
+
+
+        /*************************************************************************
+        RMS error on the test set
+
+        INPUT PARAMETERS:
+            LM      -   linear model
+            XY      -   test set
+            NPoints -   test set size
+
+        RESULT:
+            root mean square error.
+
+          -- ALGLIB --
+             Copyright 30.08.2008 by Bochkanov Sergey
+        *************************************************************************/
+        public static double lrrmserror(linearmodel lm,
+            double[,] xy,
+            int npoints)
+        {
+            double result = 0;
+            int i = 0;
+            double v = 0;
+            int offs = 0;
+            int nvars = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            alglib.ap.assert((int)Math.Round(lm.w[1])==lrvnum, "LINREG: Incorrect LINREG version!");
+            nvars = (int)Math.Round(lm.w[2]);
+            offs = (int)Math.Round(lm.w[3]);
+            result = 0;
+            for(i=0; i<=npoints-1; i++)
+            {
+                i1_ = (offs)-(0);
+                v = 0.0;
+                for(i_=0; i_<=nvars-1;i_++)
+                {
+                    v += xy[i,i_]*lm.w[i_+i1_];
+                }
+                v = v+lm.w[offs+nvars];
+                result = result+math.sqr(v-xy[i,nvars]);
+            }
+            result = Math.Sqrt(result/npoints);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Average error on the test set
+
+        INPUT PARAMETERS:
+            LM      -   linear model
+            XY      -   test set
+            NPoints -   test set size
+
+        RESULT:
+            average error.
+
+          -- ALGLIB --
+             Copyright 30.08.2008 by Bochkanov Sergey
+        *************************************************************************/
+        public static double lravgerror(linearmodel lm,
+            double[,] xy,
+            int npoints)
+        {
+            double result = 0;
+            int i = 0;
+            double v = 0;
+            int offs = 0;
+            int nvars = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            alglib.ap.assert((int)Math.Round(lm.w[1])==lrvnum, "LINREG: Incorrect LINREG version!");
+            nvars = (int)Math.Round(lm.w[2]);
+            offs = (int)Math.Round(lm.w[3]);
+            result = 0;
+            for(i=0; i<=npoints-1; i++)
+            {
+                i1_ = (offs)-(0);
+                v = 0.0;
+                for(i_=0; i_<=nvars-1;i_++)
+                {
+                    v += xy[i,i_]*lm.w[i_+i1_];
+                }
+                v = v+lm.w[offs+nvars];
+                result = result+Math.Abs(v-xy[i,nvars]);
+            }
+            result = result/npoints;
+            return result;
+        }
+
+
+        /*************************************************************************
+        RMS error on the test set
+
+        INPUT PARAMETERS:
+            LM      -   linear model
+            XY      -   test set
+            NPoints -   test set size
+
+        RESULT:
+            average relative error.
+
+          -- ALGLIB --
+             Copyright 30.08.2008 by Bochkanov Sergey
+        *************************************************************************/
+        public static double lravgrelerror(linearmodel lm,
+            double[,] xy,
+            int npoints)
+        {
+            double result = 0;
+            int i = 0;
+            int k = 0;
+            double v = 0;
+            int offs = 0;
+            int nvars = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            alglib.ap.assert((int)Math.Round(lm.w[1])==lrvnum, "LINREG: Incorrect LINREG version!");
+            nvars = (int)Math.Round(lm.w[2]);
+            offs = (int)Math.Round(lm.w[3]);
+            result = 0;
+            k = 0;
+            for(i=0; i<=npoints-1; i++)
+            {
+                if( (double)(xy[i,nvars])!=(double)(0) )
+                {
+                    i1_ = (offs)-(0);
+                    v = 0.0;
+                    for(i_=0; i_<=nvars-1;i_++)
+                    {
+                        v += xy[i,i_]*lm.w[i_+i1_];
+                    }
+                    v = v+lm.w[offs+nvars];
+                    result = result+Math.Abs((v-xy[i,nvars])/xy[i,nvars]);
+                    k = k+1;
+                }
+            }
+            if( k!=0 )
+            {
+                result = result/k;
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        Copying of LinearModel strucure
+
+        INPUT PARAMETERS:
+            LM1 -   original
+
+        OUTPUT PARAMETERS:
+            LM2 -   copy
+
+          -- ALGLIB --
+             Copyright 15.03.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void lrcopy(linearmodel lm1,
+            linearmodel lm2)
+        {
+            int k = 0;
+            int i_ = 0;
+
+            k = (int)Math.Round(lm1.w[0]);
+            lm2.w = new double[k-1+1];
+            for(i_=0; i_<=k-1;i_++)
+            {
+                lm2.w[i_] = lm1.w[i_];
+            }
+        }
+
+
+        public static void lrlines(double[,] xy,
+            double[] s,
+            int n,
+            ref int info,
+            ref double a,
+            ref double b,
+            ref double vara,
+            ref double varb,
+            ref double covab,
+            ref double corrab,
+            ref double p)
+        {
+            int i = 0;
+            double ss = 0;
+            double sx = 0;
+            double sxx = 0;
+            double sy = 0;
+            double stt = 0;
+            double e1 = 0;
+            double e2 = 0;
+            double t = 0;
+            double chi2 = 0;
+
+            info = 0;
+            a = 0;
+            b = 0;
+            vara = 0;
+            varb = 0;
+            covab = 0;
+            corrab = 0;
+            p = 0;
+
+            if( n<2 )
+            {
+                info = -1;
+                return;
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                if( (double)(s[i])<=(double)(0) )
+                {
+                    info = -2;
+                    return;
+                }
+            }
+            info = 1;
+            
+            //
+            // Calculate S, SX, SY, SXX
+            //
+            ss = 0;
+            sx = 0;
+            sy = 0;
+            sxx = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                t = math.sqr(s[i]);
+                ss = ss+1/t;
+                sx = sx+xy[i,0]/t;
+                sy = sy+xy[i,1]/t;
+                sxx = sxx+math.sqr(xy[i,0])/t;
+            }
+            
+            //
+            // Test for condition number
+            //
+            t = Math.Sqrt(4*math.sqr(sx)+math.sqr(ss-sxx));
+            e1 = 0.5*(ss+sxx+t);
+            e2 = 0.5*(ss+sxx-t);
+            if( (double)(Math.Min(e1, e2))<=(double)(1000*math.machineepsilon*Math.Max(e1, e2)) )
+            {
+                info = -3;
+                return;
+            }
+            
+            //
+            // Calculate A, B
+            //
+            a = 0;
+            b = 0;
+            stt = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                t = (xy[i,0]-sx/ss)/s[i];
+                b = b+t*xy[i,1]/s[i];
+                stt = stt+math.sqr(t);
+            }
+            b = b/stt;
+            a = (sy-sx*b)/ss;
+            
+            //
+            // Calculate goodness-of-fit
+            //
+            if( n>2 )
+            {
+                chi2 = 0;
+                for(i=0; i<=n-1; i++)
+                {
+                    chi2 = chi2+math.sqr((xy[i,1]-a-b*xy[i,0])/s[i]);
+                }
+                p = igammaf.incompletegammac((double)(n-2)/(double)2, chi2/2);
+            }
+            else
+            {
+                p = 1;
+            }
+            
+            //
+            // Calculate other parameters
+            //
+            vara = (1+math.sqr(sx)/(ss*stt))/ss;
+            varb = 1/stt;
+            covab = -(sx/(ss*stt));
+            corrab = covab/Math.Sqrt(vara*varb);
+        }
+
+
+        public static void lrline(double[,] xy,
+            int n,
+            ref int info,
+            ref double a,
+            ref double b)
+        {
+            double[] s = new double[0];
+            int i = 0;
+            double vara = 0;
+            double varb = 0;
+            double covab = 0;
+            double corrab = 0;
+            double p = 0;
+
+            info = 0;
+            a = 0;
+            b = 0;
+
+            if( n<2 )
+            {
+                info = -1;
+                return;
+            }
+            s = new double[n-1+1];
+            for(i=0; i<=n-1; i++)
+            {
+                s[i] = 1;
+            }
+            lrlines(xy, s, n, ref info, ref a, ref b, ref vara, ref varb, ref covab, ref corrab, ref p);
+        }
+
+
+        /*************************************************************************
+        Internal linear regression subroutine
+        *************************************************************************/
+        private static void lrinternal(double[,] xy,
+            double[] s,
+            int npoints,
+            int nvars,
+            ref int info,
+            linearmodel lm,
+            lrreport ar)
+        {
+            double[,] a = new double[0,0];
+            double[,] u = new double[0,0];
+            double[,] vt = new double[0,0];
+            double[,] vm = new double[0,0];
+            double[,] xym = new double[0,0];
+            double[] b = new double[0];
+            double[] sv = new double[0];
+            double[] t = new double[0];
+            double[] svi = new double[0];
+            double[] work = new double[0];
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int ncv = 0;
+            int na = 0;
+            int nacv = 0;
+            double r = 0;
+            double p = 0;
+            double epstol = 0;
+            lrreport ar2 = new lrreport();
+            int offs = 0;
+            linearmodel tlm = new linearmodel();
+            int i_ = 0;
+            int i1_ = 0;
+
+            info = 0;
+
+            epstol = 1000;
+            
+            //
+            // Check for errors in data
+            //
+            if( npoints<nvars || nvars<1 )
+            {
+                info = -1;
+                return;
+            }
+            for(i=0; i<=npoints-1; i++)
+            {
+                if( (double)(s[i])<=(double)(0) )
+                {
+                    info = -2;
+                    return;
+                }
+            }
+            info = 1;
+            
+            //
+            // Create design matrix
+            //
+            a = new double[npoints-1+1, nvars-1+1];
+            b = new double[npoints-1+1];
+            for(i=0; i<=npoints-1; i++)
+            {
+                r = 1/s[i];
+                for(i_=0; i_<=nvars-1;i_++)
+                {
+                    a[i,i_] = r*xy[i,i_];
+                }
+                b[i] = xy[i,nvars]/s[i];
+            }
+            
+            //
+            // Allocate W:
+            // W[0]     array size
+            // W[1]     version number, 0
+            // W[2]     NVars (minus 1, to be compatible with external representation)
+            // W[3]     coefficients offset
+            //
+            lm.w = new double[4+nvars-1+1];
+            offs = 4;
+            lm.w[0] = 4+nvars;
+            lm.w[1] = lrvnum;
+            lm.w[2] = nvars-1;
+            lm.w[3] = offs;
+            
+            //
+            // Solve problem using SVD:
+            //
+            // 0. check for degeneracy (different types)
+            // 1. A = U*diag(sv)*V'
+            // 2. T = b'*U
+            // 3. w = SUM((T[i]/sv[i])*V[..,i])
+            // 4. cov(wi,wj) = SUM(Vji*Vjk/sv[i]^2,K=1..M)
+            //
+            // see $15.4 of "Numerical Recipes in C" for more information
+            //
+            t = new double[nvars-1+1];
+            svi = new double[nvars-1+1];
+            ar.c = new double[nvars-1+1, nvars-1+1];
+            vm = new double[nvars-1+1, nvars-1+1];
+            if( !svd.rmatrixsvd(a, npoints, nvars, 1, 1, 2, ref sv, ref u, ref vt) )
+            {
+                info = -4;
+                return;
+            }
+            if( (double)(sv[0])<=(double)(0) )
+            {
+                
+                //
+                // Degenerate case: zero design matrix.
+                //
+                for(i=offs; i<=offs+nvars-1; i++)
+                {
+                    lm.w[i] = 0;
+                }
+                ar.rmserror = lrrmserror(lm, xy, npoints);
+                ar.avgerror = lravgerror(lm, xy, npoints);
+                ar.avgrelerror = lravgrelerror(lm, xy, npoints);
+                ar.cvrmserror = ar.rmserror;
+                ar.cvavgerror = ar.avgerror;
+                ar.cvavgrelerror = ar.avgrelerror;
+                ar.ncvdefects = 0;
+                ar.cvdefects = new int[nvars-1+1];
+                ar.c = new double[nvars-1+1, nvars-1+1];
+                for(i=0; i<=nvars-1; i++)
+                {
+                    for(j=0; j<=nvars-1; j++)
+                    {
+                        ar.c[i,j] = 0;
+                    }
+                }
+                return;
+            }
+            if( (double)(sv[nvars-1])<=(double)(epstol*math.machineepsilon*sv[0]) )
+            {
+                
+                //
+                // Degenerate case, non-zero design matrix.
+                //
+                // We can leave it and solve task in SVD least squares fashion.
+                // Solution and covariance matrix will be obtained correctly,
+                // but CV error estimates - will not. It is better to reduce
+                // it to non-degenerate task and to obtain correct CV estimates.
+                //
+                for(k=nvars; k>=1; k--)
+                {
+                    if( (double)(sv[k-1])>(double)(epstol*math.machineepsilon*sv[0]) )
+                    {
+                        
+                        //
+                        // Reduce
+                        //
+                        xym = new double[npoints-1+1, k+1];
+                        for(i=0; i<=npoints-1; i++)
+                        {
+                            for(j=0; j<=k-1; j++)
+                            {
+                                r = 0.0;
+                                for(i_=0; i_<=nvars-1;i_++)
+                                {
+                                    r += xy[i,i_]*vt[j,i_];
+                                }
+                                xym[i,j] = r;
+                            }
+                            xym[i,k] = xy[i,nvars];
+                        }
+                        
+                        //
+                        // Solve
+                        //
+                        lrinternal(xym, s, npoints, k, ref info, tlm, ar2);
+                        if( info!=1 )
+                        {
+                            return;
+                        }
+                        
+                        //
+                        // Convert back to un-reduced format
+                        //
+                        for(j=0; j<=nvars-1; j++)
+                        {
+                            lm.w[offs+j] = 0;
+                        }
+                        for(j=0; j<=k-1; j++)
+                        {
+                            r = tlm.w[offs+j];
+                            i1_ = (0) - (offs);
+                            for(i_=offs; i_<=offs+nvars-1;i_++)
+                            {
+                                lm.w[i_] = lm.w[i_] + r*vt[j,i_+i1_];
+                            }
+                        }
+                        ar.rmserror = ar2.rmserror;
+                        ar.avgerror = ar2.avgerror;
+                        ar.avgrelerror = ar2.avgrelerror;
+                        ar.cvrmserror = ar2.cvrmserror;
+                        ar.cvavgerror = ar2.cvavgerror;
+                        ar.cvavgrelerror = ar2.cvavgrelerror;
+                        ar.ncvdefects = ar2.ncvdefects;
+                        ar.cvdefects = new int[nvars-1+1];
+                        for(j=0; j<=ar.ncvdefects-1; j++)
+                        {
+                            ar.cvdefects[j] = ar2.cvdefects[j];
+                        }
+                        ar.c = new double[nvars-1+1, nvars-1+1];
+                        work = new double[nvars+1];
+                        blas.matrixmatrixmultiply(ar2.c, 0, k-1, 0, k-1, false, vt, 0, k-1, 0, nvars-1, false, 1.0, ref vm, 0, k-1, 0, nvars-1, 0.0, ref work);
+                        blas.matrixmatrixmultiply(vt, 0, k-1, 0, nvars-1, true, vm, 0, k-1, 0, nvars-1, false, 1.0, ref ar.c, 0, nvars-1, 0, nvars-1, 0.0, ref work);
+                        return;
+                    }
+                }
+                info = -255;
+                return;
+            }
+            for(i=0; i<=nvars-1; i++)
+            {
+                if( (double)(sv[i])>(double)(epstol*math.machineepsilon*sv[0]) )
+                {
+                    svi[i] = 1/sv[i];
+                }
+                else
+                {
+                    svi[i] = 0;
+                }
+            }
+            for(i=0; i<=nvars-1; i++)
+            {
+                t[i] = 0;
+            }
+            for(i=0; i<=npoints-1; i++)
+            {
+                r = b[i];
+                for(i_=0; i_<=nvars-1;i_++)
+                {
+                    t[i_] = t[i_] + r*u[i,i_];
+                }
+            }
+            for(i=0; i<=nvars-1; i++)
+            {
+                lm.w[offs+i] = 0;
+            }
+            for(i=0; i<=nvars-1; i++)
+            {
+                r = t[i]*svi[i];
+                i1_ = (0) - (offs);
+                for(i_=offs; i_<=offs+nvars-1;i_++)
+                {
+                    lm.w[i_] = lm.w[i_] + r*vt[i,i_+i1_];
+                }
+            }
+            for(j=0; j<=nvars-1; j++)
+            {
+                r = svi[j];
+                for(i_=0; i_<=nvars-1;i_++)
+                {
+                    vm[i_,j] = r*vt[j,i_];
+                }
+            }
+            for(i=0; i<=nvars-1; i++)
+            {
+                for(j=i; j<=nvars-1; j++)
+                {
+                    r = 0.0;
+                    for(i_=0; i_<=nvars-1;i_++)
+                    {
+                        r += vm[i,i_]*vm[j,i_];
+                    }
+                    ar.c[i,j] = r;
+                    ar.c[j,i] = r;
+                }
+            }
+            
+            //
+            // Leave-1-out cross-validation error.
+            //
+            // NOTATIONS:
+            // A            design matrix
+            // A*x = b      original linear least squares task
+            // U*S*V'       SVD of A
+            // ai           i-th row of the A
+            // bi           i-th element of the b
+            // xf           solution of the original LLS task
+            //
+            // Cross-validation error of i-th element from a sample is
+            // calculated using following formula:
+            //
+            //     ERRi = ai*xf - (ai*xf-bi*(ui*ui'))/(1-ui*ui')     (1)
+            //
+            // This formula can be derived from normal equations of the
+            // original task
+            //
+            //     (A'*A)x = A'*b                                    (2)
+            //
+            // by applying modification (zeroing out i-th row of A) to (2):
+            //
+            //     (A-ai)'*(A-ai) = (A-ai)'*b
+            //
+            // and using Sherman-Morrison formula for updating matrix inverse
+            //
+            // NOTE 1: b is not zeroed out since it is much simpler and
+            // does not influence final result.
+            //
+            // NOTE 2: some design matrices A have such ui that 1-ui*ui'=0.
+            // Formula (1) can't be applied for such cases and they are skipped
+            // from CV calculation (which distorts resulting CV estimate).
+            // But from the properties of U we can conclude that there can
+            // be no more than NVars such vectors. Usually
+            // NVars << NPoints, so in a normal case it only slightly
+            // influences result.
+            //
+            ncv = 0;
+            na = 0;
+            nacv = 0;
+            ar.rmserror = 0;
+            ar.avgerror = 0;
+            ar.avgrelerror = 0;
+            ar.cvrmserror = 0;
+            ar.cvavgerror = 0;
+            ar.cvavgrelerror = 0;
+            ar.ncvdefects = 0;
+            ar.cvdefects = new int[nvars-1+1];
+            for(i=0; i<=npoints-1; i++)
+            {
+                
+                //
+                // Error on a training set
+                //
+                i1_ = (offs)-(0);
+                r = 0.0;
+                for(i_=0; i_<=nvars-1;i_++)
+                {
+                    r += xy[i,i_]*lm.w[i_+i1_];
+                }
+                ar.rmserror = ar.rmserror+math.sqr(r-xy[i,nvars]);
+                ar.avgerror = ar.avgerror+Math.Abs(r-xy[i,nvars]);
+                if( (double)(xy[i,nvars])!=(double)(0) )
+                {
+                    ar.avgrelerror = ar.avgrelerror+Math.Abs((r-xy[i,nvars])/xy[i,nvars]);
+                    na = na+1;
+                }
+                
+                //
+                // Error using fast leave-one-out cross-validation
+                //
+                p = 0.0;
+                for(i_=0; i_<=nvars-1;i_++)
+                {
+                    p += u[i,i_]*u[i,i_];
+                }
+                if( (double)(p)>(double)(1-epstol*math.machineepsilon) )
+                {
+                    ar.cvdefects[ar.ncvdefects] = i;
+                    ar.ncvdefects = ar.ncvdefects+1;
+                    continue;
+                }
+                r = s[i]*(r/s[i]-b[i]*p)/(1-p);
+                ar.cvrmserror = ar.cvrmserror+math.sqr(r-xy[i,nvars]);
+                ar.cvavgerror = ar.cvavgerror+Math.Abs(r-xy[i,nvars]);
+                if( (double)(xy[i,nvars])!=(double)(0) )
+                {
+                    ar.cvavgrelerror = ar.cvavgrelerror+Math.Abs((r-xy[i,nvars])/xy[i,nvars]);
+                    nacv = nacv+1;
+                }
+                ncv = ncv+1;
+            }
+            if( ncv==0 )
+            {
+                
+                //
+                // Something strange: ALL ui are degenerate.
+                // Unexpected...
+                //
+                info = -255;
+                return;
+            }
+            ar.rmserror = Math.Sqrt(ar.rmserror/npoints);
+            ar.avgerror = ar.avgerror/npoints;
+            if( na!=0 )
+            {
+                ar.avgrelerror = ar.avgrelerror/na;
+            }
+            ar.cvrmserror = Math.Sqrt(ar.cvrmserror/ncv);
+            ar.cvavgerror = ar.cvavgerror/ncv;
+            if( nacv!=0 )
+            {
+                ar.cvavgrelerror = ar.cvavgrelerror/nacv;
+            }
+        }
+
+
+    }
+    public class filters
+    {
+        /*************************************************************************
+        Filters: simple moving averages (unsymmetric).
+
+        This filter replaces array by results of SMA(K) filter. SMA(K) is defined
+        as filter which averages at most K previous points (previous - not points
+        AROUND central point) - or less, in case of the first K-1 points.
+
+        INPUT PARAMETERS:
+            X           -   array[N], array to process. It can be larger than N,
+                            in this case only first N points are processed.
+            N           -   points count, N>=0
+            K           -   K>=1 (K can be larger than N ,  such  cases  will  be
+                            correctly handled). Window width. K=1 corresponds  to
+                            identity transformation (nothing changes).
+
+        OUTPUT PARAMETERS:
+            X           -   array, whose first N elements were processed with SMA(K)
+
+        NOTE 1: this function uses efficient in-place  algorithm  which  does not
+                allocate temporary arrays.
+
+        NOTE 2: this algorithm makes only one pass through array and uses running
+                sum  to speed-up calculation of the averages. Additional measures
+                are taken to ensure that running sum on a long sequence  of  zero
+                elements will be correctly reset to zero even in the presence  of
+                round-off error.
+
+        NOTE 3: this  is  unsymmetric version of the algorithm,  which  does  NOT
+                averages points after the current one. Only X[i], X[i-1], ... are
+                used when calculating new value of X[i]. We should also note that
+                this algorithm uses BOTH previous points and  current  one,  i.e.
+                new value of X[i] depends on BOTH previous point and X[i] itself.
+
+          -- ALGLIB --
+             Copyright 25.10.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void filtersma(ref double[] x,
+            int n,
+            int k)
+        {
+            int i = 0;
+            double runningsum = 0;
+            double termsinsum = 0;
+            int zeroprefix = 0;
+            double v = 0;
+
+            alglib.ap.assert(n>=0, "FilterSMA: N<0");
+            alglib.ap.assert(alglib.ap.len(x)>=n, "FilterSMA: Length(X)<N");
+            alglib.ap.assert(apserv.isfinitevector(x, n), "FilterSMA: X contains INF or NAN");
+            alglib.ap.assert(k>=1, "FilterSMA: K<1");
+            
+            //
+            // Quick exit, if necessary
+            //
+            if( n<=1 || k==1 )
+            {
+                return;
+            }
+            
+            //
+            // Prepare variables (see below for explanation)
+            //
+            runningsum = 0.0;
+            termsinsum = 0;
+            for(i=Math.Max(n-k, 0); i<=n-1; i++)
+            {
+                runningsum = runningsum+x[i];
+                termsinsum = termsinsum+1;
+            }
+            i = Math.Max(n-k, 0);
+            zeroprefix = 0;
+            while( i<=n-1 && (double)(x[i])==(double)(0) )
+            {
+                zeroprefix = zeroprefix+1;
+                i = i+1;
+            }
+            
+            //
+            // General case: we assume that N>1 and K>1
+            //
+            // Make one pass through all elements. At the beginning of
+            // the iteration we have:
+            // * I              element being processed
+            // * RunningSum     current value of the running sum
+            //                  (including I-th element)
+            // * TermsInSum     number of terms in sum, 0<=TermsInSum<=K
+            // * ZeroPrefix     length of the sequence of zero elements
+            //                  which starts at X[I-K+1] and continues towards X[I].
+            //                  Equal to zero in case X[I-K+1] is non-zero.
+            //                  This value is used to make RunningSum exactly zero
+            //                  when it follows from the problem properties.
+            //
+            for(i=n-1; i>=0; i--)
+            {
+                
+                //
+                // Store new value of X[i], save old value in V
+                //
+                v = x[i];
+                x[i] = runningsum/termsinsum;
+                
+                //
+                // Update RunningSum and TermsInSum
+                //
+                if( i-k>=0 )
+                {
+                    runningsum = runningsum-v+x[i-k];
+                }
+                else
+                {
+                    runningsum = runningsum-v;
+                    termsinsum = termsinsum-1;
+                }
+                
+                //
+                // Update ZeroPrefix.
+                // In case we have ZeroPrefix=TermsInSum,
+                // RunningSum is reset to zero.
+                //
+                if( i-k>=0 )
+                {
+                    if( (double)(x[i-k])!=(double)(0) )
+                    {
+                        zeroprefix = 0;
+                    }
+                    else
+                    {
+                        zeroprefix = Math.Min(zeroprefix+1, k);
+                    }
+                }
+                else
+                {
+                    zeroprefix = Math.Min(zeroprefix, i+1);
+                }
+                if( (double)(zeroprefix)==(double)(termsinsum) )
+                {
+                    runningsum = 0;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Filters: exponential moving averages.
+
+        This filter replaces array by results of EMA(alpha) filter. EMA(alpha) is
+        defined as filter which replaces X[] by S[]:
+            S[0] = X[0]
+            S[t] = alpha*X[t] + (1-alpha)*S[t-1]
+
+        INPUT PARAMETERS:
+            X           -   array[N], array to process. It can be larger than N,
+                            in this case only first N points are processed.
+            N           -   points count, N>=0
+            alpha       -   0<alpha<=1, smoothing parameter.
+
+        OUTPUT PARAMETERS:
+            X           -   array, whose first N elements were processed
+                            with EMA(alpha)
+
+        NOTE 1: this function uses efficient in-place  algorithm  which  does not
+                allocate temporary arrays.
+
+        NOTE 2: this algorithm uses BOTH previous points and  current  one,  i.e.
+                new value of X[i] depends on BOTH previous point and X[i] itself.
+
+        NOTE 3: technical analytis users quite often work  with  EMA  coefficient
+                expressed in DAYS instead of fractions. If you want to  calculate
+                EMA(N), where N is a number of days, you can use alpha=2/(N+1).
+
+          -- ALGLIB --
+             Copyright 25.10.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void filterema(ref double[] x,
+            int n,
+            double alpha)
+        {
+            int i = 0;
+
+            alglib.ap.assert(n>=0, "FilterEMA: N<0");
+            alglib.ap.assert(alglib.ap.len(x)>=n, "FilterEMA: Length(X)<N");
+            alglib.ap.assert(apserv.isfinitevector(x, n), "FilterEMA: X contains INF or NAN");
+            alglib.ap.assert((double)(alpha)>(double)(0), "FilterEMA: Alpha<=0");
+            alglib.ap.assert((double)(alpha)<=(double)(1), "FilterEMA: Alpha>1");
+            
+            //
+            // Quick exit, if necessary
+            //
+            if( n<=1 || (double)(alpha)==(double)(1) )
+            {
+                return;
+            }
+            
+            //
+            // Process
+            //
+            for(i=1; i<=n-1; i++)
+            {
+                x[i] = alpha*x[i]+(1-alpha)*x[i-1];
+            }
+        }
+
+
+        /*************************************************************************
+        Filters: linear regression moving averages.
+
+        This filter replaces array by results of LRMA(K) filter.
+
+        LRMA(K) is defined as filter which, for each data  point,  builds  linear
+        regression  model  using  K  prevous  points (point itself is included in
+        these K points) and calculates value of this linear model at the point in
+        question.
+
+        INPUT PARAMETERS:
+            X           -   array[N], array to process. It can be larger than N,
+                            in this case only first N points are processed.
+            N           -   points count, N>=0
+            K           -   K>=1 (K can be larger than N ,  such  cases  will  be
+                            correctly handled). Window width. K=1 corresponds  to
+                            identity transformation (nothing changes).
+
+        OUTPUT PARAMETERS:
+            X           -   array, whose first N elements were processed with SMA(K)
+
+        NOTE 1: this function uses efficient in-place  algorithm  which  does not
+                allocate temporary arrays.
+
+        NOTE 2: this algorithm makes only one pass through array and uses running
+                sum  to speed-up calculation of the averages. Additional measures
+                are taken to ensure that running sum on a long sequence  of  zero
+                elements will be correctly reset to zero even in the presence  of
+                round-off error.
+
+        NOTE 3: this  is  unsymmetric version of the algorithm,  which  does  NOT
+                averages points after the current one. Only X[i], X[i-1], ... are
+                used when calculating new value of X[i]. We should also note that
+                this algorithm uses BOTH previous points and  current  one,  i.e.
+                new value of X[i] depends on BOTH previous point and X[i] itself.
+
+          -- ALGLIB --
+             Copyright 25.10.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void filterlrma(ref double[] x,
+            int n,
+            int k)
+        {
+            int i = 0;
+            int m = 0;
+            double[,] xy = new double[0,0];
+            double[] s = new double[0];
+            int info = 0;
+            double a = 0;
+            double b = 0;
+            double vara = 0;
+            double varb = 0;
+            double covab = 0;
+            double corrab = 0;
+            double p = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            alglib.ap.assert(n>=0, "FilterLRMA: N<0");
+            alglib.ap.assert(alglib.ap.len(x)>=n, "FilterLRMA: Length(X)<N");
+            alglib.ap.assert(apserv.isfinitevector(x, n), "FilterLRMA: X contains INF or NAN");
+            alglib.ap.assert(k>=1, "FilterLRMA: K<1");
+            
+            //
+            // Quick exit, if necessary:
+            // * either N is equal to 1 (nothing to average)
+            // * or K is 1 (only point itself is used) or 2 (model is too simple,
+            //   we will always get identity transformation)
+            //
+            if( n<=1 || k<=2 )
+            {
+                return;
+            }
+            
+            //
+            // General case: K>2, N>1.
+            // We do not process points with I<2 because first two points (I=0 and I=1) will be
+            // left unmodified by LRMA filter in any case.
+            //
+            xy = new double[k, 2];
+            s = new double[k];
+            for(i=0; i<=k-1; i++)
+            {
+                xy[i,0] = i;
+                s[i] = 1.0;
+            }
+            for(i=n-1; i>=2; i--)
+            {
+                m = Math.Min(i+1, k);
+                i1_ = (i-m+1) - (0);
+                for(i_=0; i_<=m-1;i_++)
+                {
+                    xy[i_,1] = x[i_+i1_];
+                }
+                linreg.lrlines(xy, s, m, ref info, ref a, ref b, ref vara, ref varb, ref covab, ref corrab, ref p);
+                alglib.ap.assert(info==1, "FilterLRMA: internal error");
+                x[i] = a+b*(m-1);
+            }
+        }
+
+
+    }
+    public class kmeans
+    {
+        /*************************************************************************
+        k-means++ clusterization
+
+        INPUT PARAMETERS:
+            XY          -   dataset, array [0..NPoints-1,0..NVars-1].
+            NPoints     -   dataset size, NPoints>=K
+            NVars       -   number of variables, NVars>=1
+            K           -   desired number of clusters, K>=1
+            Restarts    -   number of restarts, Restarts>=1
+
+        OUTPUT PARAMETERS:
+            Info        -   return code:
+                            * -3, if task is degenerate (number of distinct points is
+                                  less than K)
+                            * -1, if incorrect NPoints/NFeatures/K/Restarts was passed
+                            *  1, if subroutine finished successfully
+            C           -   array[0..NVars-1,0..K-1].matrix whose columns store
+                            cluster's centers
+            XYC         -   array[NPoints], which contains cluster indexes
+
+          -- ALGLIB --
+             Copyright 21.03.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void kmeansgenerate(double[,] xy,
+            int npoints,
+            int nvars,
+            int k,
+            int restarts,
+            ref int info,
+            ref double[,] c,
+            ref int[] xyc)
+        {
+            int i = 0;
+            int j = 0;
+            double[,] ct = new double[0,0];
+            double[,] ctbest = new double[0,0];
+            int[] xycbest = new int[0];
+            double e = 0;
+            double ebest = 0;
+            double[] x = new double[0];
+            double[] tmp = new double[0];
+            double[] d2 = new double[0];
+            double[] p = new double[0];
+            int[] csizes = new int[0];
+            bool[] cbusy = new bool[0];
+            double v = 0;
+            int cclosest = 0;
+            double dclosest = 0;
+            double[] work = new double[0];
+            bool waschanges = new bool();
+            bool zerosizeclusters = new bool();
+            int pass = 0;
+            int i_ = 0;
+
+            info = 0;
+            c = new double[0,0];
+            xyc = new int[0];
+
+            
+            //
+            // Test parameters
+            //
+            if( ((npoints<k || nvars<1) || k<1) || restarts<1 )
+            {
+                info = -1;
+                return;
+            }
+            
+            //
+            // TODO: special case K=1
+            // TODO: special case K=NPoints
+            //
+            info = 1;
+            
+            //
+            // Multiple passes of k-means++ algorithm
+            //
+            ct = new double[k, nvars];
+            ctbest = new double[k, nvars];
+            xyc = new int[npoints];
+            xycbest = new int[npoints];
+            d2 = new double[npoints];
+            p = new double[npoints];
+            tmp = new double[nvars];
+            csizes = new int[k];
+            cbusy = new bool[k];
+            ebest = math.maxrealnumber;
+            for(pass=1; pass<=restarts; pass++)
+            {
+                
+                //
+                // Select initial centers  using k-means++ algorithm
+                // 1. Choose first center at random
+                // 2. Choose next centers using their distance from centers already chosen
+                //
+                // Note that for performance reasons centers are stored in ROWS of CT, not
+                // in columns. We'll transpose CT in the end and store it in the C.
+                //
+                i = math.randominteger(npoints);
+                for(i_=0; i_<=nvars-1;i_++)
+                {
+                    ct[0,i_] = xy[i,i_];
+                }
+                cbusy[0] = true;
+                for(i=1; i<=k-1; i++)
+                {
+                    cbusy[i] = false;
+                }
+                if( !selectcenterpp(xy, npoints, nvars, ref ct, cbusy, k, ref d2, ref p, ref tmp) )
+                {
+                    info = -3;
+                    return;
+                }
+                
+                //
+                // Update centers:
+                // 2. update center positions
+                //
+                for(i=0; i<=npoints-1; i++)
+                {
+                    xyc[i] = -1;
+                }
+                while( true )
+                {
+                    
+                    //
+                    // fill XYC with center numbers
+                    //
+                    waschanges = false;
+                    for(i=0; i<=npoints-1; i++)
+                    {
+                        cclosest = -1;
+                        dclosest = math.maxrealnumber;
+                        for(j=0; j<=k-1; j++)
+                        {
+                            for(i_=0; i_<=nvars-1;i_++)
+                            {
+                                tmp[i_] = xy[i,i_];
+                            }
+                            for(i_=0; i_<=nvars-1;i_++)
+                            {
+                                tmp[i_] = tmp[i_] - ct[j,i_];
+                            }
+                            v = 0.0;
+                            for(i_=0; i_<=nvars-1;i_++)
+                            {
+                                v += tmp[i_]*tmp[i_];
+                            }
+                            if( (double)(v)<(double)(dclosest) )
+                            {
+                                cclosest = j;
+                                dclosest = v;
+                            }
+                        }
+                        if( xyc[i]!=cclosest )
+                        {
+                            waschanges = true;
+                        }
+                        xyc[i] = cclosest;
+                    }
+                    
+                    //
+                    // Update centers
+                    //
+                    for(j=0; j<=k-1; j++)
+                    {
+                        csizes[j] = 0;
+                    }
+                    for(i=0; i<=k-1; i++)
+                    {
+                        for(j=0; j<=nvars-1; j++)
+                        {
+                            ct[i,j] = 0;
+                        }
+                    }
+                    for(i=0; i<=npoints-1; i++)
+                    {
+                        csizes[xyc[i]] = csizes[xyc[i]]+1;
+                        for(i_=0; i_<=nvars-1;i_++)
+                        {
+                            ct[xyc[i],i_] = ct[xyc[i],i_] + xy[i,i_];
+                        }
+                    }
+                    zerosizeclusters = false;
+                    for(i=0; i<=k-1; i++)
+                    {
+                        cbusy[i] = csizes[i]!=0;
+                        zerosizeclusters = zerosizeclusters || csizes[i]==0;
+                    }
+                    if( zerosizeclusters )
+                    {
+                        
+                        //
+                        // Some clusters have zero size - rare, but possible.
+                        // We'll choose new centers for such clusters using k-means++ rule
+                        // and restart algorithm
+                        //
+                        if( !selectcenterpp(xy, npoints, nvars, ref ct, cbusy, k, ref d2, ref p, ref tmp) )
+                        {
+                            info = -3;
+                            return;
+                        }
+                        continue;
+                    }
+                    for(j=0; j<=k-1; j++)
+                    {
+                        v = (double)1/(double)csizes[j];
+                        for(i_=0; i_<=nvars-1;i_++)
+                        {
+                            ct[j,i_] = v*ct[j,i_];
+                        }
+                    }
+                    
+                    //
+                    // if nothing has changed during iteration
+                    //
+                    if( !waschanges )
+                    {
+                        break;
+                    }
+                }
+                
+                //
+                // 3. Calculate E, compare with best centers found so far
+                //
+                e = 0;
+                for(i=0; i<=npoints-1; i++)
+                {
+                    for(i_=0; i_<=nvars-1;i_++)
+                    {
+                        tmp[i_] = xy[i,i_];
+                    }
+                    for(i_=0; i_<=nvars-1;i_++)
+                    {
+                        tmp[i_] = tmp[i_] - ct[xyc[i],i_];
+                    }
+                    v = 0.0;
+                    for(i_=0; i_<=nvars-1;i_++)
+                    {
+                        v += tmp[i_]*tmp[i_];
+                    }
+                    e = e+v;
+                }
+                if( (double)(e)<(double)(ebest) )
+                {
+                    
+                    //
+                    // store partition.
+                    //
+                    ebest = e;
+                    blas.copymatrix(ct, 0, k-1, 0, nvars-1, ref ctbest, 0, k-1, 0, nvars-1);
+                    for(i=0; i<=npoints-1; i++)
+                    {
+                        xycbest[i] = xyc[i];
+                    }
+                }
+            }
+            
+            //
+            // Copy and transpose
+            //
+            c = new double[nvars-1+1, k-1+1];
+            blas.copyandtranspose(ctbest, 0, k-1, 0, nvars-1, ref c, 0, nvars-1, 0, k-1);
+            for(i=0; i<=npoints-1; i++)
+            {
+                xyc[i] = xycbest[i];
+            }
+        }
+
+
+        /*************************************************************************
+        Select center for a new cluster using k-means++ rule
+        *************************************************************************/
+        private static bool selectcenterpp(double[,] xy,
+            int npoints,
+            int nvars,
+            ref double[,] centers,
+            bool[] busycenters,
+            int ccnt,
+            ref double[] d2,
+            ref double[] p,
+            ref double[] tmp)
+        {
+            bool result = new bool();
+            int i = 0;
+            int j = 0;
+            int cc = 0;
+            double v = 0;
+            double s = 0;
+            int i_ = 0;
+
+            busycenters = (bool[])busycenters.Clone();
+
+            result = true;
+            for(cc=0; cc<=ccnt-1; cc++)
+            {
+                if( !busycenters[cc] )
+                {
+                    
+                    //
+                    // fill D2
+                    //
+                    for(i=0; i<=npoints-1; i++)
+                    {
+                        d2[i] = math.maxrealnumber;
+                        for(j=0; j<=ccnt-1; j++)
+                        {
+                            if( busycenters[j] )
+                            {
+                                for(i_=0; i_<=nvars-1;i_++)
+                                {
+                                    tmp[i_] = xy[i,i_];
+                                }
+                                for(i_=0; i_<=nvars-1;i_++)
+                                {
+                                    tmp[i_] = tmp[i_] - centers[j,i_];
+                                }
+                                v = 0.0;
+                                for(i_=0; i_<=nvars-1;i_++)
+                                {
+                                    v += tmp[i_]*tmp[i_];
+                                }
+                                if( (double)(v)<(double)(d2[i]) )
+                                {
+                                    d2[i] = v;
+                                }
+                            }
+                        }
+                    }
+                    
+                    //
+                    // calculate P (non-cumulative)
+                    //
+                    s = 0;
+                    for(i=0; i<=npoints-1; i++)
+                    {
+                        s = s+d2[i];
+                    }
+                    if( (double)(s)==(double)(0) )
+                    {
+                        result = false;
+                        return result;
+                    }
+                    s = 1/s;
+                    for(i_=0; i_<=npoints-1;i_++)
+                    {
+                        p[i_] = s*d2[i_];
+                    }
+                    
+                    //
+                    // choose one of points with probability P
+                    // random number within (0,1) is generated and
+                    // inverse empirical CDF is used to randomly choose a point.
+                    //
+                    s = 0;
+                    v = math.randomreal();
+                    for(i=0; i<=npoints-1; i++)
+                    {
+                        s = s+p[i];
+                        if( (double)(v)<=(double)(s) || i==npoints-1 )
+                        {
+                            for(i_=0; i_<=nvars-1;i_++)
+                            {
+                                centers[cc,i_] = xy[i,i_];
+                            }
+                            busycenters[cc] = true;
+                            break;
+                        }
+                    }
+                }
+            }
+            return result;
+        }
+
+
+    }
+    public class lda
+    {
+        /*************************************************************************
+        Multiclass Fisher LDA
+
+        Subroutine finds coefficients of linear combination which optimally separates
+        training set on classes.
+
+        INPUT PARAMETERS:
+            XY          -   training set, array[0..NPoints-1,0..NVars].
+                            First NVars columns store values of independent
+                            variables, next column stores number of class (from 0
+                            to NClasses-1) which dataset element belongs to. Fractional
+                            values are rounded to nearest integer.
+            NPoints     -   training set size, NPoints>=0
+            NVars       -   number of independent variables, NVars>=1
+            NClasses    -   number of classes, NClasses>=2
+
+
+        OUTPUT PARAMETERS:
+            Info        -   return code:
+                            * -4, if internal EVD subroutine hasn't converged
+                            * -2, if there is a point with class number
+                                  outside of [0..NClasses-1].
+                            * -1, if incorrect parameters was passed (NPoints<0,
+                                  NVars<1, NClasses<2)
+                            *  1, if task has been solved
+                            *  2, if there was a multicollinearity in training set,
+                                  but task has been solved.
+            W           -   linear combination coefficients, array[0..NVars-1]
+
+          -- ALGLIB --
+             Copyright 31.05.2008 by Bochkanov Sergey
+        *************************************************************************/
+        public static void fisherlda(double[,] xy,
+            int npoints,
+            int nvars,
+            int nclasses,
+            ref int info,
+            ref double[] w)
+        {
+            double[,] w2 = new double[0,0];
+            int i_ = 0;
+
+            info = 0;
+            w = new double[0];
+
+            fisherldan(xy, npoints, nvars, nclasses, ref info, ref w2);
+            if( info>0 )
+            {
+                w = new double[nvars-1+1];
+                for(i_=0; i_<=nvars-1;i_++)
+                {
+                    w[i_] = w2[i_,0];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        N-dimensional multiclass Fisher LDA
+
+        Subroutine finds coefficients of linear combinations which optimally separates
+        training set on classes. It returns N-dimensional basis whose vector are sorted
+        by quality of training set separation (in descending order).
+
+        INPUT PARAMETERS:
+            XY          -   training set, array[0..NPoints-1,0..NVars].
+                            First NVars columns store values of independent
+                            variables, next column stores number of class (from 0
+                            to NClasses-1) which dataset element belongs to. Fractional
+                            values are rounded to nearest integer.
+            NPoints     -   training set size, NPoints>=0
+            NVars       -   number of independent variables, NVars>=1
+            NClasses    -   number of classes, NClasses>=2
+
+
+        OUTPUT PARAMETERS:
+            Info        -   return code:
+                            * -4, if internal EVD subroutine hasn't converged
+                            * -2, if there is a point with class number
+                                  outside of [0..NClasses-1].
+                            * -1, if incorrect parameters was passed (NPoints<0,
+                                  NVars<1, NClasses<2)
+                            *  1, if task has been solved
+                            *  2, if there was a multicollinearity in training set,
+                                  but task has been solved.
+            W           -   basis, array[0..NVars-1,0..NVars-1]
+                            columns of matrix stores basis vectors, sorted by
+                            quality of training set separation (in descending order)
+
+          -- ALGLIB --
+             Copyright 31.05.2008 by Bochkanov Sergey
+        *************************************************************************/
+        public static void fisherldan(double[,] xy,
+            int npoints,
+            int nvars,
+            int nclasses,
+            ref int info,
+            ref double[,] w)
+        {
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int m = 0;
+            double v = 0;
+            int[] c = new int[0];
+            double[] mu = new double[0];
+            double[,] muc = new double[0,0];
+            int[] nc = new int[0];
+            double[,] sw = new double[0,0];
+            double[,] st = new double[0,0];
+            double[,] z = new double[0,0];
+            double[,] z2 = new double[0,0];
+            double[,] tm = new double[0,0];
+            double[,] sbroot = new double[0,0];
+            double[,] a = new double[0,0];
+            double[,] xyproj = new double[0,0];
+            double[,] wproj = new double[0,0];
+            double[] tf = new double[0];
+            double[] d = new double[0];
+            double[] d2 = new double[0];
+            double[] work = new double[0];
+            int i_ = 0;
+
+            info = 0;
+            w = new double[0,0];
+
+            
+            //
+            // Test data
+            //
+            if( (npoints<0 || nvars<1) || nclasses<2 )
+            {
+                info = -1;
+                return;
+            }
+            for(i=0; i<=npoints-1; i++)
+            {
+                if( (int)Math.Round(xy[i,nvars])<0 || (int)Math.Round(xy[i,nvars])>=nclasses )
+                {
+                    info = -2;
+                    return;
+                }
+            }
+            info = 1;
+            
+            //
+            // Special case: NPoints<=1
+            // Degenerate task.
+            //
+            if( npoints<=1 )
+            {
+                info = 2;
+                w = new double[nvars-1+1, nvars-1+1];
+                for(i=0; i<=nvars-1; i++)
+                {
+                    for(j=0; j<=nvars-1; j++)
+                    {
+                        if( i==j )
+                        {
+                            w[i,j] = 1;
+                        }
+                        else
+                        {
+                            w[i,j] = 0;
+                        }
+                    }
+                }
+                return;
+            }
+            
+            //
+            // Prepare temporaries
+            //
+            tf = new double[nvars-1+1];
+            work = new double[Math.Max(nvars, npoints)+1];
+            
+            //
+            // Convert class labels from reals to integers (just for convenience)
+            //
+            c = new int[npoints-1+1];
+            for(i=0; i<=npoints-1; i++)
+            {
+                c[i] = (int)Math.Round(xy[i,nvars]);
+            }
+            
+            //
+            // Calculate class sizes and means
+            //
+            mu = new double[nvars-1+1];
+            muc = new double[nclasses-1+1, nvars-1+1];
+            nc = new int[nclasses-1+1];
+            for(j=0; j<=nvars-1; j++)
+            {
+                mu[j] = 0;
+            }
+            for(i=0; i<=nclasses-1; i++)
+            {
+                nc[i] = 0;
+                for(j=0; j<=nvars-1; j++)
+                {
+                    muc[i,j] = 0;
+                }
+            }
+            for(i=0; i<=npoints-1; i++)
+            {
+                for(i_=0; i_<=nvars-1;i_++)
+                {
+                    mu[i_] = mu[i_] + xy[i,i_];
+                }
+                for(i_=0; i_<=nvars-1;i_++)
+                {
+                    muc[c[i],i_] = muc[c[i],i_] + xy[i,i_];
+                }
+                nc[c[i]] = nc[c[i]]+1;
+            }
+            for(i=0; i<=nclasses-1; i++)
+            {
+                v = (double)1/(double)nc[i];
+                for(i_=0; i_<=nvars-1;i_++)
+                {
+                    muc[i,i_] = v*muc[i,i_];
+                }
+            }
+            v = (double)1/(double)npoints;
+            for(i_=0; i_<=nvars-1;i_++)
+            {
+                mu[i_] = v*mu[i_];
+            }
+            
+            //
+            // Create ST matrix
+            //
+            st = new double[nvars-1+1, nvars-1+1];
+            for(i=0; i<=nvars-1; i++)
+            {
+                for(j=0; j<=nvars-1; j++)
+                {
+                    st[i,j] = 0;
+                }
+            }
+            for(k=0; k<=npoints-1; k++)
+            {
+                for(i_=0; i_<=nvars-1;i_++)
+                {
+                    tf[i_] = xy[k,i_];
+                }
+                for(i_=0; i_<=nvars-1;i_++)
+                {
+                    tf[i_] = tf[i_] - mu[i_];
+                }
+                for(i=0; i<=nvars-1; i++)
+                {
+                    v = tf[i];
+                    for(i_=0; i_<=nvars-1;i_++)
+                    {
+                        st[i,i_] = st[i,i_] + v*tf[i_];
+                    }
+                }
+            }
+            
+            //
+            // Create SW matrix
+            //
+            sw = new double[nvars-1+1, nvars-1+1];
+            for(i=0; i<=nvars-1; i++)
+            {
+                for(j=0; j<=nvars-1; j++)
+                {
+                    sw[i,j] = 0;
+                }
+            }
+            for(k=0; k<=npoints-1; k++)
+            {
+                for(i_=0; i_<=nvars-1;i_++)
+                {
+                    tf[i_] = xy[k,i_];
+                }
+                for(i_=0; i_<=nvars-1;i_++)
+                {
+                    tf[i_] = tf[i_] - muc[c[k],i_];
+                }
+                for(i=0; i<=nvars-1; i++)
+                {
+                    v = tf[i];
+                    for(i_=0; i_<=nvars-1;i_++)
+                    {
+                        sw[i,i_] = sw[i,i_] + v*tf[i_];
+                    }
+                }
+            }
+            
+            //
+            // Maximize ratio J=(w'*ST*w)/(w'*SW*w).
+            //
+            // First, make transition from w to v such that w'*ST*w becomes v'*v:
+            //    v  = root(ST)*w = R*w
+            //    R  = root(D)*Z'
+            //    w  = (root(ST)^-1)*v = RI*v
+            //    RI = Z*inv(root(D))
+            //    J  = (v'*v)/(v'*(RI'*SW*RI)*v)
+            //    ST = Z*D*Z'
+            //
+            //    so we have
+            //
+            //    J = (v'*v) / (v'*(inv(root(D))*Z'*SW*Z*inv(root(D)))*v)  =
+            //      = (v'*v) / (v'*A*v)
+            //
+            if( !evd.smatrixevd(st, nvars, 1, true, ref d, ref z) )
+            {
+                info = -4;
+                return;
+            }
+            w = new double[nvars-1+1, nvars-1+1];
+            if( (double)(d[nvars-1])<=(double)(0) || (double)(d[0])<=(double)(1000*math.machineepsilon*d[nvars-1]) )
+            {
+                
+                //
+                // Special case: D[NVars-1]<=0
+                // Degenerate task (all variables takes the same value).
+                //
+                if( (double)(d[nvars-1])<=(double)(0) )
+                {
+                    info = 2;
+                    for(i=0; i<=nvars-1; i++)
+                    {
+                        for(j=0; j<=nvars-1; j++)
+                        {
+                            if( i==j )
+                            {
+                                w[i,j] = 1;
+                            }
+                            else
+                            {
+                                w[i,j] = 0;
+                            }
+                        }
+                    }
+                    return;
+                }
+                
+                //
+                // Special case: degenerate ST matrix, multicollinearity found.
+                // Since we know ST eigenvalues/vectors we can translate task to
+                // non-degenerate form.
+                //
+                // Let WG is orthogonal basis of the non zero variance subspace
+                // of the ST and let WZ is orthogonal basis of the zero variance
+                // subspace.
+                //
+                // Projection on WG allows us to use LDA on reduced M-dimensional
+                // subspace, N-M vectors of WZ allows us to update reduced LDA
+                // factors to full N-dimensional subspace.
+                //
+                m = 0;
+                for(k=0; k<=nvars-1; k++)
+                {
+                    if( (double)(d[k])<=(double)(1000*math.machineepsilon*d[nvars-1]) )
+                    {
+                        m = k+1;
+                    }
+                }
+                alglib.ap.assert(m!=0, "FisherLDAN: internal error #1");
+                xyproj = new double[npoints-1+1, nvars-m+1];
+                blas.matrixmatrixmultiply(xy, 0, npoints-1, 0, nvars-1, false, z, 0, nvars-1, m, nvars-1, false, 1.0, ref xyproj, 0, npoints-1, 0, nvars-m-1, 0.0, ref work);
+                for(i=0; i<=npoints-1; i++)
+                {
+                    xyproj[i,nvars-m] = xy[i,nvars];
+                }
+                fisherldan(xyproj, npoints, nvars-m, nclasses, ref info, ref wproj);
+                if( info<0 )
+                {
+                    return;
+                }
+                blas.matrixmatrixmultiply(z, 0, nvars-1, m, nvars-1, false, wproj, 0, nvars-m-1, 0, nvars-m-1, false, 1.0, ref w, 0, nvars-1, 0, nvars-m-1, 0.0, ref work);
+                for(k=nvars-m; k<=nvars-1; k++)
+                {
+                    for(i_=0; i_<=nvars-1;i_++)
+                    {
+                        w[i_,k] = z[i_,k-(nvars-m)];
+                    }
+                }
+                info = 2;
+            }
+            else
+            {
+                
+                //
+                // General case: no multicollinearity
+                //
+                tm = new double[nvars-1+1, nvars-1+1];
+                a = new double[nvars-1+1, nvars-1+1];
+                blas.matrixmatrixmultiply(sw, 0, nvars-1, 0, nvars-1, false, z, 0, nvars-1, 0, nvars-1, false, 1.0, ref tm, 0, nvars-1, 0, nvars-1, 0.0, ref work);
+                blas.matrixmatrixmultiply(z, 0, nvars-1, 0, nvars-1, true, tm, 0, nvars-1, 0, nvars-1, false, 1.0, ref a, 0, nvars-1, 0, nvars-1, 0.0, ref work);
+                for(i=0; i<=nvars-1; i++)
+                {
+                    for(j=0; j<=nvars-1; j++)
+                    {
+                        a[i,j] = a[i,j]/Math.Sqrt(d[i]*d[j]);
+                    }
+                }
+                if( !evd.smatrixevd(a, nvars, 1, true, ref d2, ref z2) )
+                {
+                    info = -4;
+                    return;
+                }
+                for(k=0; k<=nvars-1; k++)
+                {
+                    for(i=0; i<=nvars-1; i++)
+                    {
+                        tf[i] = z2[i,k]/Math.Sqrt(d[i]);
+                    }
+                    for(i=0; i<=nvars-1; i++)
+                    {
+                        v = 0.0;
+                        for(i_=0; i_<=nvars-1;i_++)
+                        {
+                            v += z[i,i_]*tf[i_];
+                        }
+                        w[i,k] = v;
+                    }
+                }
+            }
+            
+            //
+            // Post-processing:
+            // * normalization
+            // * converting to non-negative form, if possible
+            //
+            for(k=0; k<=nvars-1; k++)
+            {
+                v = 0.0;
+                for(i_=0; i_<=nvars-1;i_++)
+                {
+                    v += w[i_,k]*w[i_,k];
+                }
+                v = 1/Math.Sqrt(v);
+                for(i_=0; i_<=nvars-1;i_++)
+                {
+                    w[i_,k] = v*w[i_,k];
+                }
+                v = 0;
+                for(i=0; i<=nvars-1; i++)
+                {
+                    v = v+w[i,k];
+                }
+                if( (double)(v)<(double)(0) )
+                {
+                    for(i_=0; i_<=nvars-1;i_++)
+                    {
+                        w[i_,k] = -1*w[i_,k];
+                    }
+                }
+            }
+        }
+
+
+    }
+    public class mlpbase
+    {
+        public class multilayerperceptron
+        {
+            public int hlnetworktype;
+            public int hlnormtype;
+            public int[] hllayersizes;
+            public int[] hlconnections;
+            public int[] hlneurons;
+            public int[] structinfo;
+            public double[] weights;
+            public double[] columnmeans;
+            public double[] columnsigmas;
+            public double[] neurons;
+            public double[] dfdnet;
+            public double[] derror;
+            public double[] x;
+            public double[] y;
+            public double[,] chunks;
+            public double[] nwbuf;
+            public int[] integerbuf;
+            public multilayerperceptron()
+            {
+                hllayersizes = new int[0];
+                hlconnections = new int[0];
+                hlneurons = new int[0];
+                structinfo = new int[0];
+                weights = new double[0];
+                columnmeans = new double[0];
+                columnsigmas = new double[0];
+                neurons = new double[0];
+                dfdnet = new double[0];
+                derror = new double[0];
+                x = new double[0];
+                y = new double[0];
+                chunks = new double[0,0];
+                nwbuf = new double[0];
+                integerbuf = new int[0];
+            }
+        };
+
+
+
+
+        public const int mlpvnum = 7;
+        public const int mlpfirstversion = 0;
+        public const int nfieldwidth = 4;
+        public const int hlconnfieldwidth = 5;
+        public const int hlnfieldwidth = 4;
+        public const int chunksize = 32;
+
+
+        /*************************************************************************
+        Creates  neural  network  with  NIn  inputs,  NOut outputs, without hidden
+        layers, with linear output layer. Network weights are  filled  with  small
+        random values.
+
+          -- ALGLIB --
+             Copyright 04.11.2007 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpcreate0(int nin,
+            int nout,
+            multilayerperceptron network)
+        {
+            int[] lsizes = new int[0];
+            int[] ltypes = new int[0];
+            int[] lconnfirst = new int[0];
+            int[] lconnlast = new int[0];
+            int layerscount = 0;
+            int lastproc = 0;
+
+            layerscount = 1+3;
+            
+            //
+            // Allocate arrays
+            //
+            lsizes = new int[layerscount-1+1];
+            ltypes = new int[layerscount-1+1];
+            lconnfirst = new int[layerscount-1+1];
+            lconnlast = new int[layerscount-1+1];
+            
+            //
+            // Layers
+            //
+            addinputlayer(nin, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc);
+            addbiasedsummatorlayer(nout, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc);
+            addactivationlayer(-5, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc);
+            
+            //
+            // Create
+            //
+            mlpcreate(nin, nout, lsizes, ltypes, lconnfirst, lconnlast, layerscount, false, network);
+            fillhighlevelinformation(network, nin, 0, 0, nout, false, true);
+        }
+
+
+        /*************************************************************************
+        Same  as  MLPCreate0,  but  with  one  hidden  layer  (NHid  neurons) with
+        non-linear activation function. Output layer is linear.
+
+          -- ALGLIB --
+             Copyright 04.11.2007 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpcreate1(int nin,
+            int nhid,
+            int nout,
+            multilayerperceptron network)
+        {
+            int[] lsizes = new int[0];
+            int[] ltypes = new int[0];
+            int[] lconnfirst = new int[0];
+            int[] lconnlast = new int[0];
+            int layerscount = 0;
+            int lastproc = 0;
+
+            layerscount = 1+3+3;
+            
+            //
+            // Allocate arrays
+            //
+            lsizes = new int[layerscount-1+1];
+            ltypes = new int[layerscount-1+1];
+            lconnfirst = new int[layerscount-1+1];
+            lconnlast = new int[layerscount-1+1];
+            
+            //
+            // Layers
+            //
+            addinputlayer(nin, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc);
+            addbiasedsummatorlayer(nhid, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc);
+            addactivationlayer(1, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc);
+            addbiasedsummatorlayer(nout, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc);
+            addactivationlayer(-5, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc);
+            
+            //
+            // Create
+            //
+            mlpcreate(nin, nout, lsizes, ltypes, lconnfirst, lconnlast, layerscount, false, network);
+            fillhighlevelinformation(network, nin, nhid, 0, nout, false, true);
+        }
+
+
+        /*************************************************************************
+        Same as MLPCreate0, but with two hidden layers (NHid1 and  NHid2  neurons)
+        with non-linear activation function. Output layer is linear.
+         $ALL
+
+          -- ALGLIB --
+             Copyright 04.11.2007 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpcreate2(int nin,
+            int nhid1,
+            int nhid2,
+            int nout,
+            multilayerperceptron network)
+        {
+            int[] lsizes = new int[0];
+            int[] ltypes = new int[0];
+            int[] lconnfirst = new int[0];
+            int[] lconnlast = new int[0];
+            int layerscount = 0;
+            int lastproc = 0;
+
+            layerscount = 1+3+3+3;
+            
+            //
+            // Allocate arrays
+            //
+            lsizes = new int[layerscount-1+1];
+            ltypes = new int[layerscount-1+1];
+            lconnfirst = new int[layerscount-1+1];
+            lconnlast = new int[layerscount-1+1];
+            
+            //
+            // Layers
+            //
+            addinputlayer(nin, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc);
+            addbiasedsummatorlayer(nhid1, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc);
+            addactivationlayer(1, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc);
+            addbiasedsummatorlayer(nhid2, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc);
+            addactivationlayer(1, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc);
+            addbiasedsummatorlayer(nout, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc);
+            addactivationlayer(-5, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc);
+            
+            //
+            // Create
+            //
+            mlpcreate(nin, nout, lsizes, ltypes, lconnfirst, lconnlast, layerscount, false, network);
+            fillhighlevelinformation(network, nin, nhid1, nhid2, nout, false, true);
+        }
+
+
+        /*************************************************************************
+        Creates  neural  network  with  NIn  inputs,  NOut outputs, without hidden
+        layers with non-linear output layer. Network weights are filled with small
+        random values.
+
+        Activation function of the output layer takes values:
+
+            (B, +INF), if D>=0
+
+        or
+
+            (-INF, B), if D<0.
+
+
+          -- ALGLIB --
+             Copyright 30.03.2008 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpcreateb0(int nin,
+            int nout,
+            double b,
+            double d,
+            multilayerperceptron network)
+        {
+            int[] lsizes = new int[0];
+            int[] ltypes = new int[0];
+            int[] lconnfirst = new int[0];
+            int[] lconnlast = new int[0];
+            int layerscount = 0;
+            int lastproc = 0;
+            int i = 0;
+
+            layerscount = 1+3;
+            if( (double)(d)>=(double)(0) )
+            {
+                d = 1;
+            }
+            else
+            {
+                d = -1;
+            }
+            
+            //
+            // Allocate arrays
+            //
+            lsizes = new int[layerscount-1+1];
+            ltypes = new int[layerscount-1+1];
+            lconnfirst = new int[layerscount-1+1];
+            lconnlast = new int[layerscount-1+1];
+            
+            //
+            // Layers
+            //
+            addinputlayer(nin, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc);
+            addbiasedsummatorlayer(nout, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc);
+            addactivationlayer(3, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc);
+            
+            //
+            // Create
+            //
+            mlpcreate(nin, nout, lsizes, ltypes, lconnfirst, lconnlast, layerscount, false, network);
+            fillhighlevelinformation(network, nin, 0, 0, nout, false, false);
+            
+            //
+            // Turn on ouputs shift/scaling.
+            //
+            for(i=nin; i<=nin+nout-1; i++)
+            {
+                network.columnmeans[i] = b;
+                network.columnsigmas[i] = d;
+            }
+        }
+
+
+        /*************************************************************************
+        Same as MLPCreateB0 but with non-linear hidden layer.
+
+          -- ALGLIB --
+             Copyright 30.03.2008 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpcreateb1(int nin,
+            int nhid,
+            int nout,
+            double b,
+            double d,
+            multilayerperceptron network)
+        {
+            int[] lsizes = new int[0];
+            int[] ltypes = new int[0];
+            int[] lconnfirst = new int[0];
+            int[] lconnlast = new int[0];
+            int layerscount = 0;
+            int lastproc = 0;
+            int i = 0;
+
+            layerscount = 1+3+3;
+            if( (double)(d)>=(double)(0) )
+            {
+                d = 1;
+            }
+            else
+            {
+                d = -1;
+            }
+            
+            //
+            // Allocate arrays
+            //
+            lsizes = new int[layerscount-1+1];
+            ltypes = new int[layerscount-1+1];
+            lconnfirst = new int[layerscount-1+1];
+            lconnlast = new int[layerscount-1+1];
+            
+            //
+            // Layers
+            //
+            addinputlayer(nin, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc);
+            addbiasedsummatorlayer(nhid, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc);
+            addactivationlayer(1, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc);
+            addbiasedsummatorlayer(nout, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc);
+            addactivationlayer(3, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc);
+            
+            //
+            // Create
+            //
+            mlpcreate(nin, nout, lsizes, ltypes, lconnfirst, lconnlast, layerscount, false, network);
+            fillhighlevelinformation(network, nin, nhid, 0, nout, false, false);
+            
+            //
+            // Turn on ouputs shift/scaling.
+            //
+            for(i=nin; i<=nin+nout-1; i++)
+            {
+                network.columnmeans[i] = b;
+                network.columnsigmas[i] = d;
+            }
+        }
+
+
+        /*************************************************************************
+        Same as MLPCreateB0 but with two non-linear hidden layers.
+
+          -- ALGLIB --
+             Copyright 30.03.2008 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpcreateb2(int nin,
+            int nhid1,
+            int nhid2,
+            int nout,
+            double b,
+            double d,
+            multilayerperceptron network)
+        {
+            int[] lsizes = new int[0];
+            int[] ltypes = new int[0];
+            int[] lconnfirst = new int[0];
+            int[] lconnlast = new int[0];
+            int layerscount = 0;
+            int lastproc = 0;
+            int i = 0;
+
+            layerscount = 1+3+3+3;
+            if( (double)(d)>=(double)(0) )
+            {
+                d = 1;
+            }
+            else
+            {
+                d = -1;
+            }
+            
+            //
+            // Allocate arrays
+            //
+            lsizes = new int[layerscount-1+1];
+            ltypes = new int[layerscount-1+1];
+            lconnfirst = new int[layerscount-1+1];
+            lconnlast = new int[layerscount-1+1];
+            
+            //
+            // Layers
+            //
+            addinputlayer(nin, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc);
+            addbiasedsummatorlayer(nhid1, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc);
+            addactivationlayer(1, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc);
+            addbiasedsummatorlayer(nhid2, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc);
+            addactivationlayer(1, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc);
+            addbiasedsummatorlayer(nout, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc);
+            addactivationlayer(3, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc);
+            
+            //
+            // Create
+            //
+            mlpcreate(nin, nout, lsizes, ltypes, lconnfirst, lconnlast, layerscount, false, network);
+            fillhighlevelinformation(network, nin, nhid1, nhid2, nout, false, false);
+            
+            //
+            // Turn on ouputs shift/scaling.
+            //
+            for(i=nin; i<=nin+nout-1; i++)
+            {
+                network.columnmeans[i] = b;
+                network.columnsigmas[i] = d;
+            }
+        }
+
+
+        /*************************************************************************
+        Creates  neural  network  with  NIn  inputs,  NOut outputs, without hidden
+        layers with non-linear output layer. Network weights are filled with small
+        random values. Activation function of the output layer takes values [A,B].
+
+          -- ALGLIB --
+             Copyright 30.03.2008 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpcreater0(int nin,
+            int nout,
+            double a,
+            double b,
+            multilayerperceptron network)
+        {
+            int[] lsizes = new int[0];
+            int[] ltypes = new int[0];
+            int[] lconnfirst = new int[0];
+            int[] lconnlast = new int[0];
+            int layerscount = 0;
+            int lastproc = 0;
+            int i = 0;
+
+            layerscount = 1+3;
+            
+            //
+            // Allocate arrays
+            //
+            lsizes = new int[layerscount-1+1];
+            ltypes = new int[layerscount-1+1];
+            lconnfirst = new int[layerscount-1+1];
+            lconnlast = new int[layerscount-1+1];
+            
+            //
+            // Layers
+            //
+            addinputlayer(nin, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc);
+            addbiasedsummatorlayer(nout, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc);
+            addactivationlayer(1, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc);
+            
+            //
+            // Create
+            //
+            mlpcreate(nin, nout, lsizes, ltypes, lconnfirst, lconnlast, layerscount, false, network);
+            fillhighlevelinformation(network, nin, 0, 0, nout, false, false);
+            
+            //
+            // Turn on outputs shift/scaling.
+            //
+            for(i=nin; i<=nin+nout-1; i++)
+            {
+                network.columnmeans[i] = 0.5*(a+b);
+                network.columnsigmas[i] = 0.5*(a-b);
+            }
+        }
+
+
+        /*************************************************************************
+        Same as MLPCreateR0, but with non-linear hidden layer.
+
+          -- ALGLIB --
+             Copyright 30.03.2008 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpcreater1(int nin,
+            int nhid,
+            int nout,
+            double a,
+            double b,
+            multilayerperceptron network)
+        {
+            int[] lsizes = new int[0];
+            int[] ltypes = new int[0];
+            int[] lconnfirst = new int[0];
+            int[] lconnlast = new int[0];
+            int layerscount = 0;
+            int lastproc = 0;
+            int i = 0;
+
+            layerscount = 1+3+3;
+            
+            //
+            // Allocate arrays
+            //
+            lsizes = new int[layerscount-1+1];
+            ltypes = new int[layerscount-1+1];
+            lconnfirst = new int[layerscount-1+1];
+            lconnlast = new int[layerscount-1+1];
+            
+            //
+            // Layers
+            //
+            addinputlayer(nin, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc);
+            addbiasedsummatorlayer(nhid, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc);
+            addactivationlayer(1, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc);
+            addbiasedsummatorlayer(nout, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc);
+            addactivationlayer(1, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc);
+            
+            //
+            // Create
+            //
+            mlpcreate(nin, nout, lsizes, ltypes, lconnfirst, lconnlast, layerscount, false, network);
+            fillhighlevelinformation(network, nin, nhid, 0, nout, false, false);
+            
+            //
+            // Turn on outputs shift/scaling.
+            //
+            for(i=nin; i<=nin+nout-1; i++)
+            {
+                network.columnmeans[i] = 0.5*(a+b);
+                network.columnsigmas[i] = 0.5*(a-b);
+            }
+        }
+
+
+        /*************************************************************************
+        Same as MLPCreateR0, but with two non-linear hidden layers.
+
+          -- ALGLIB --
+             Copyright 30.03.2008 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpcreater2(int nin,
+            int nhid1,
+            int nhid2,
+            int nout,
+            double a,
+            double b,
+            multilayerperceptron network)
+        {
+            int[] lsizes = new int[0];
+            int[] ltypes = new int[0];
+            int[] lconnfirst = new int[0];
+            int[] lconnlast = new int[0];
+            int layerscount = 0;
+            int lastproc = 0;
+            int i = 0;
+
+            layerscount = 1+3+3+3;
+            
+            //
+            // Allocate arrays
+            //
+            lsizes = new int[layerscount-1+1];
+            ltypes = new int[layerscount-1+1];
+            lconnfirst = new int[layerscount-1+1];
+            lconnlast = new int[layerscount-1+1];
+            
+            //
+            // Layers
+            //
+            addinputlayer(nin, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc);
+            addbiasedsummatorlayer(nhid1, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc);
+            addactivationlayer(1, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc);
+            addbiasedsummatorlayer(nhid2, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc);
+            addactivationlayer(1, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc);
+            addbiasedsummatorlayer(nout, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc);
+            addactivationlayer(1, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc);
+            
+            //
+            // Create
+            //
+            mlpcreate(nin, nout, lsizes, ltypes, lconnfirst, lconnlast, layerscount, false, network);
+            fillhighlevelinformation(network, nin, nhid1, nhid2, nout, false, false);
+            
+            //
+            // Turn on outputs shift/scaling.
+            //
+            for(i=nin; i<=nin+nout-1; i++)
+            {
+                network.columnmeans[i] = 0.5*(a+b);
+                network.columnsigmas[i] = 0.5*(a-b);
+            }
+        }
+
+
+        /*************************************************************************
+        Creates classifier network with NIn  inputs  and  NOut  possible  classes.
+        Network contains no hidden layers and linear output  layer  with  SOFTMAX-
+        normalization  (so  outputs  sums  up  to  1.0  and  converge to posterior
+        probabilities).
+
+          -- ALGLIB --
+             Copyright 04.11.2007 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpcreatec0(int nin,
+            int nout,
+            multilayerperceptron network)
+        {
+            int[] lsizes = new int[0];
+            int[] ltypes = new int[0];
+            int[] lconnfirst = new int[0];
+            int[] lconnlast = new int[0];
+            int layerscount = 0;
+            int lastproc = 0;
+
+            alglib.ap.assert(nout>=2, "MLPCreateC0: NOut<2!");
+            layerscount = 1+2+1;
+            
+            //
+            // Allocate arrays
+            //
+            lsizes = new int[layerscount-1+1];
+            ltypes = new int[layerscount-1+1];
+            lconnfirst = new int[layerscount-1+1];
+            lconnlast = new int[layerscount-1+1];
+            
+            //
+            // Layers
+            //
+            addinputlayer(nin, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc);
+            addbiasedsummatorlayer(nout-1, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc);
+            addzerolayer(ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc);
+            
+            //
+            // Create
+            //
+            mlpcreate(nin, nout, lsizes, ltypes, lconnfirst, lconnlast, layerscount, true, network);
+            fillhighlevelinformation(network, nin, 0, 0, nout, true, true);
+        }
+
+
+        /*************************************************************************
+        Same as MLPCreateC0, but with one non-linear hidden layer.
+
+          -- ALGLIB --
+             Copyright 04.11.2007 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpcreatec1(int nin,
+            int nhid,
+            int nout,
+            multilayerperceptron network)
+        {
+            int[] lsizes = new int[0];
+            int[] ltypes = new int[0];
+            int[] lconnfirst = new int[0];
+            int[] lconnlast = new int[0];
+            int layerscount = 0;
+            int lastproc = 0;
+
+            alglib.ap.assert(nout>=2, "MLPCreateC1: NOut<2!");
+            layerscount = 1+3+2+1;
+            
+            //
+            // Allocate arrays
+            //
+            lsizes = new int[layerscount-1+1];
+            ltypes = new int[layerscount-1+1];
+            lconnfirst = new int[layerscount-1+1];
+            lconnlast = new int[layerscount-1+1];
+            
+            //
+            // Layers
+            //
+            addinputlayer(nin, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc);
+            addbiasedsummatorlayer(nhid, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc);
+            addactivationlayer(1, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc);
+            addbiasedsummatorlayer(nout-1, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc);
+            addzerolayer(ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc);
+            
+            //
+            // Create
+            //
+            mlpcreate(nin, nout, lsizes, ltypes, lconnfirst, lconnlast, layerscount, true, network);
+            fillhighlevelinformation(network, nin, nhid, 0, nout, true, true);
+        }
+
+
+        /*************************************************************************
+        Same as MLPCreateC0, but with two non-linear hidden layers.
+
+          -- ALGLIB --
+             Copyright 04.11.2007 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpcreatec2(int nin,
+            int nhid1,
+            int nhid2,
+            int nout,
+            multilayerperceptron network)
+        {
+            int[] lsizes = new int[0];
+            int[] ltypes = new int[0];
+            int[] lconnfirst = new int[0];
+            int[] lconnlast = new int[0];
+            int layerscount = 0;
+            int lastproc = 0;
+
+            alglib.ap.assert(nout>=2, "MLPCreateC2: NOut<2!");
+            layerscount = 1+3+3+2+1;
+            
+            //
+            // Allocate arrays
+            //
+            lsizes = new int[layerscount-1+1];
+            ltypes = new int[layerscount-1+1];
+            lconnfirst = new int[layerscount-1+1];
+            lconnlast = new int[layerscount-1+1];
+            
+            //
+            // Layers
+            //
+            addinputlayer(nin, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc);
+            addbiasedsummatorlayer(nhid1, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc);
+            addactivationlayer(1, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc);
+            addbiasedsummatorlayer(nhid2, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc);
+            addactivationlayer(1, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc);
+            addbiasedsummatorlayer(nout-1, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc);
+            addzerolayer(ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc);
+            
+            //
+            // Create
+            //
+            mlpcreate(nin, nout, lsizes, ltypes, lconnfirst, lconnlast, layerscount, true, network);
+            fillhighlevelinformation(network, nin, nhid1, nhid2, nout, true, true);
+        }
+
+
+        /*************************************************************************
+        Copying of neural network
+
+        INPUT PARAMETERS:
+            Network1 -   original
+
+        OUTPUT PARAMETERS:
+            Network2 -   copy
+
+          -- ALGLIB --
+             Copyright 04.11.2007 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpcopy(multilayerperceptron network1,
+            multilayerperceptron network2)
+        {
+            network2.hlnetworktype = network1.hlnetworktype;
+            network2.hlnormtype = network1.hlnormtype;
+            apserv.copyintegerarray(network1.hllayersizes, ref network2.hllayersizes);
+            apserv.copyintegerarray(network1.hlconnections, ref network2.hlconnections);
+            apserv.copyintegerarray(network1.hlneurons, ref network2.hlneurons);
+            apserv.copyintegerarray(network1.structinfo, ref network2.structinfo);
+            apserv.copyrealarray(network1.weights, ref network2.weights);
+            apserv.copyrealarray(network1.columnmeans, ref network2.columnmeans);
+            apserv.copyrealarray(network1.columnsigmas, ref network2.columnsigmas);
+            apserv.copyrealarray(network1.neurons, ref network2.neurons);
+            apserv.copyrealarray(network1.dfdnet, ref network2.dfdnet);
+            apserv.copyrealarray(network1.derror, ref network2.derror);
+            apserv.copyrealarray(network1.x, ref network2.x);
+            apserv.copyrealarray(network1.y, ref network2.y);
+            apserv.copyrealmatrix(network1.chunks, ref network2.chunks);
+            apserv.copyrealarray(network1.nwbuf, ref network2.nwbuf);
+            apserv.copyintegerarray(network1.integerbuf, ref network2.integerbuf);
+        }
+
+
+        /*************************************************************************
+        Serialization of MultiLayerPerceptron strucure
+
+        INPUT PARAMETERS:
+            Network -   original
+
+        OUTPUT PARAMETERS:
+            RA      -   array of real numbers which stores network,
+                        array[0..RLen-1]
+            RLen    -   RA lenght
+
+          -- ALGLIB --
+             Copyright 29.03.2008 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpserializeold(multilayerperceptron network,
+            ref double[] ra,
+            ref int rlen)
+        {
+            int i = 0;
+            int ssize = 0;
+            int ntotal = 0;
+            int nin = 0;
+            int nout = 0;
+            int wcount = 0;
+            int sigmalen = 0;
+            int offs = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            ra = new double[0];
+            rlen = 0;
+
+            
+            //
+            // Unload info
+            //
+            ssize = network.structinfo[0];
+            nin = network.structinfo[1];
+            nout = network.structinfo[2];
+            ntotal = network.structinfo[3];
+            wcount = network.structinfo[4];
+            if( mlpissoftmax(network) )
+            {
+                sigmalen = nin;
+            }
+            else
+            {
+                sigmalen = nin+nout;
+            }
+            
+            //
+            //  RA format:
+            //      LEN         DESRC.
+            //      1           RLen
+            //      1           version (MLPVNum)
+            //      1           StructInfo size
+            //      SSize       StructInfo
+            //      WCount      Weights
+            //      SigmaLen    ColumnMeans
+            //      SigmaLen    ColumnSigmas
+            //
+            rlen = 3+ssize+wcount+2*sigmalen;
+            ra = new double[rlen-1+1];
+            ra[0] = rlen;
+            ra[1] = mlpvnum;
+            ra[2] = ssize;
+            offs = 3;
+            for(i=0; i<=ssize-1; i++)
+            {
+                ra[offs+i] = network.structinfo[i];
+            }
+            offs = offs+ssize;
+            i1_ = (0) - (offs);
+            for(i_=offs; i_<=offs+wcount-1;i_++)
+            {
+                ra[i_] = network.weights[i_+i1_];
+            }
+            offs = offs+wcount;
+            i1_ = (0) - (offs);
+            for(i_=offs; i_<=offs+sigmalen-1;i_++)
+            {
+                ra[i_] = network.columnmeans[i_+i1_];
+            }
+            offs = offs+sigmalen;
+            i1_ = (0) - (offs);
+            for(i_=offs; i_<=offs+sigmalen-1;i_++)
+            {
+                ra[i_] = network.columnsigmas[i_+i1_];
+            }
+            offs = offs+sigmalen;
+        }
+
+
+        /*************************************************************************
+        Unserialization of MultiLayerPerceptron strucure
+
+        INPUT PARAMETERS:
+            RA      -   real array which stores network
+
+        OUTPUT PARAMETERS:
+            Network -   restored network
+
+          -- ALGLIB --
+             Copyright 29.03.2008 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpunserializeold(double[] ra,
+            multilayerperceptron network)
+        {
+            int i = 0;
+            int ssize = 0;
+            int ntotal = 0;
+            int nin = 0;
+            int nout = 0;
+            int wcount = 0;
+            int sigmalen = 0;
+            int offs = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            alglib.ap.assert((int)Math.Round(ra[1])==mlpvnum, "MLPUnserialize: incorrect array!");
+            
+            //
+            // Unload StructInfo from IA
+            //
+            offs = 3;
+            ssize = (int)Math.Round(ra[2]);
+            network.structinfo = new int[ssize-1+1];
+            for(i=0; i<=ssize-1; i++)
+            {
+                network.structinfo[i] = (int)Math.Round(ra[offs+i]);
+            }
+            offs = offs+ssize;
+            
+            //
+            // Unload info from StructInfo
+            //
+            ssize = network.structinfo[0];
+            nin = network.structinfo[1];
+            nout = network.structinfo[2];
+            ntotal = network.structinfo[3];
+            wcount = network.structinfo[4];
+            if( network.structinfo[6]==0 )
+            {
+                sigmalen = nin+nout;
+            }
+            else
+            {
+                sigmalen = nin;
+            }
+            
+            //
+            // Allocate space for other fields
+            //
+            network.weights = new double[wcount-1+1];
+            network.columnmeans = new double[sigmalen-1+1];
+            network.columnsigmas = new double[sigmalen-1+1];
+            network.neurons = new double[ntotal-1+1];
+            network.chunks = new double[3*ntotal+1, chunksize-1+1];
+            network.nwbuf = new double[Math.Max(wcount, 2*nout)-1+1];
+            network.dfdnet = new double[ntotal-1+1];
+            network.x = new double[nin-1+1];
+            network.y = new double[nout-1+1];
+            network.derror = new double[ntotal-1+1];
+            
+            //
+            // Copy parameters from RA
+            //
+            i1_ = (offs) - (0);
+            for(i_=0; i_<=wcount-1;i_++)
+            {
+                network.weights[i_] = ra[i_+i1_];
+            }
+            offs = offs+wcount;
+            i1_ = (offs) - (0);
+            for(i_=0; i_<=sigmalen-1;i_++)
+            {
+                network.columnmeans[i_] = ra[i_+i1_];
+            }
+            offs = offs+sigmalen;
+            i1_ = (offs) - (0);
+            for(i_=0; i_<=sigmalen-1;i_++)
+            {
+                network.columnsigmas[i_] = ra[i_+i1_];
+            }
+            offs = offs+sigmalen;
+        }
+
+
+        /*************************************************************************
+        Randomization of neural network weights
+
+          -- ALGLIB --
+             Copyright 06.11.2007 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlprandomize(multilayerperceptron network)
+        {
+            int i = 0;
+            int nin = 0;
+            int nout = 0;
+            int wcount = 0;
+
+            mlpproperties(network, ref nin, ref nout, ref wcount);
+            for(i=0; i<=wcount-1; i++)
+            {
+                network.weights[i] = math.randomreal()-0.5;
+            }
+        }
+
+
+        /*************************************************************************
+        Randomization of neural network weights and standartisator
+
+          -- ALGLIB --
+             Copyright 10.03.2008 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlprandomizefull(multilayerperceptron network)
+        {
+            int i = 0;
+            int nin = 0;
+            int nout = 0;
+            int wcount = 0;
+            int ntotal = 0;
+            int istart = 0;
+            int offs = 0;
+            int ntype = 0;
+
+            mlpproperties(network, ref nin, ref nout, ref wcount);
+            ntotal = network.structinfo[3];
+            istart = network.structinfo[5];
+            
+            //
+            // Process network
+            //
+            for(i=0; i<=wcount-1; i++)
+            {
+                network.weights[i] = math.randomreal()-0.5;
+            }
+            for(i=0; i<=nin-1; i++)
+            {
+                network.columnmeans[i] = 2*math.randomreal()-1;
+                network.columnsigmas[i] = 1.5*math.randomreal()+0.5;
+            }
+            if( !mlpissoftmax(network) )
+            {
+                for(i=0; i<=nout-1; i++)
+                {
+                    offs = istart+(ntotal-nout+i)*nfieldwidth;
+                    ntype = network.structinfo[offs+0];
+                    if( ntype==0 )
+                    {
+                        
+                        //
+                        // Shifts are changed only for linear outputs neurons
+                        //
+                        network.columnmeans[nin+i] = 2*math.randomreal()-1;
+                    }
+                    if( ntype==0 || ntype==3 )
+                    {
+                        
+                        //
+                        // Scales are changed only for linear or bounded outputs neurons.
+                        // Note that scale randomization preserves sign.
+                        //
+                        network.columnsigmas[nin+i] = Math.Sign(network.columnsigmas[nin+i])*(1.5*math.randomreal()+0.5);
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Internal subroutine.
+
+          -- ALGLIB --
+             Copyright 30.03.2008 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpinitpreprocessor(multilayerperceptron network,
+            double[,] xy,
+            int ssize)
+        {
+            int i = 0;
+            int j = 0;
+            int jmax = 0;
+            int nin = 0;
+            int nout = 0;
+            int wcount = 0;
+            int ntotal = 0;
+            int istart = 0;
+            int offs = 0;
+            int ntype = 0;
+            double[] means = new double[0];
+            double[] sigmas = new double[0];
+            double s = 0;
+
+            mlpproperties(network, ref nin, ref nout, ref wcount);
+            ntotal = network.structinfo[3];
+            istart = network.structinfo[5];
+            
+            //
+            // Means/Sigmas
+            //
+            if( mlpissoftmax(network) )
+            {
+                jmax = nin-1;
+            }
+            else
+            {
+                jmax = nin+nout-1;
+            }
+            means = new double[jmax+1];
+            sigmas = new double[jmax+1];
+            for(j=0; j<=jmax; j++)
+            {
+                means[j] = 0;
+                for(i=0; i<=ssize-1; i++)
+                {
+                    means[j] = means[j]+xy[i,j];
+                }
+                means[j] = means[j]/ssize;
+                sigmas[j] = 0;
+                for(i=0; i<=ssize-1; i++)
+                {
+                    sigmas[j] = sigmas[j]+math.sqr(xy[i,j]-means[j]);
+                }
+                sigmas[j] = Math.Sqrt(sigmas[j]/ssize);
+            }
+            
+            //
+            // Inputs
+            //
+            for(i=0; i<=nin-1; i++)
+            {
+                network.columnmeans[i] = means[i];
+                network.columnsigmas[i] = sigmas[i];
+                if( (double)(network.columnsigmas[i])==(double)(0) )
+                {
+                    network.columnsigmas[i] = 1;
+                }
+            }
+            
+            //
+            // Outputs
+            //
+            if( !mlpissoftmax(network) )
+            {
+                for(i=0; i<=nout-1; i++)
+                {
+                    offs = istart+(ntotal-nout+i)*nfieldwidth;
+                    ntype = network.structinfo[offs+0];
+                    
+                    //
+                    // Linear outputs
+                    //
+                    if( ntype==0 )
+                    {
+                        network.columnmeans[nin+i] = means[nin+i];
+                        network.columnsigmas[nin+i] = sigmas[nin+i];
+                        if( (double)(network.columnsigmas[nin+i])==(double)(0) )
+                        {
+                            network.columnsigmas[nin+i] = 1;
+                        }
+                    }
+                    
+                    //
+                    // Bounded outputs (half-interval)
+                    //
+                    if( ntype==3 )
+                    {
+                        s = means[nin+i]-network.columnmeans[nin+i];
+                        if( (double)(s)==(double)(0) )
+                        {
+                            s = Math.Sign(network.columnsigmas[nin+i]);
+                        }
+                        if( (double)(s)==(double)(0) )
+                        {
+                            s = 1.0;
+                        }
+                        network.columnsigmas[nin+i] = Math.Sign(network.columnsigmas[nin+i])*Math.Abs(s);
+                        if( (double)(network.columnsigmas[nin+i])==(double)(0) )
+                        {
+                            network.columnsigmas[nin+i] = 1;
+                        }
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Returns information about initialized network: number of inputs, outputs,
+        weights.
+
+          -- ALGLIB --
+             Copyright 04.11.2007 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpproperties(multilayerperceptron network,
+            ref int nin,
+            ref int nout,
+            ref int wcount)
+        {
+            nin = 0;
+            nout = 0;
+            wcount = 0;
+
+            nin = network.structinfo[1];
+            nout = network.structinfo[2];
+            wcount = network.structinfo[4];
+        }
+
+
+        /*************************************************************************
+        Returns number of inputs.
+
+          -- ALGLIB --
+             Copyright 19.10.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static int mlpgetinputscount(multilayerperceptron network)
+        {
+            int result = 0;
+
+            result = network.structinfo[1];
+            return result;
+        }
+
+
+        /*************************************************************************
+        Returns number of outputs.
+
+          -- ALGLIB --
+             Copyright 19.10.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static int mlpgetoutputscount(multilayerperceptron network)
+        {
+            int result = 0;
+
+            result = network.structinfo[2];
+            return result;
+        }
+
+
+        /*************************************************************************
+        Returns number of weights.
+
+          -- ALGLIB --
+             Copyright 19.10.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static int mlpgetweightscount(multilayerperceptron network)
+        {
+            int result = 0;
+
+            result = network.structinfo[4];
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tells whether network is SOFTMAX-normalized (i.e. classifier) or not.
+
+          -- ALGLIB --
+             Copyright 04.11.2007 by Bochkanov Sergey
+        *************************************************************************/
+        public static bool mlpissoftmax(multilayerperceptron network)
+        {
+            bool result = new bool();
+
+            result = network.structinfo[6]==1;
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function returns total number of layers (including input, hidden and
+        output layers).
+
+          -- ALGLIB --
+             Copyright 25.03.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static int mlpgetlayerscount(multilayerperceptron network)
+        {
+            int result = 0;
+
+            result = alglib.ap.len(network.hllayersizes);
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function returns size of K-th layer.
+
+        K=0 corresponds to input layer, K=CNT-1 corresponds to output layer.
+
+        Size of the output layer is always equal to the number of outputs, although
+        when we have softmax-normalized network, last neuron doesn't have any
+        connections - it is just zero.
+
+          -- ALGLIB --
+             Copyright 25.03.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static int mlpgetlayersize(multilayerperceptron network,
+            int k)
+        {
+            int result = 0;
+
+            alglib.ap.assert(k>=0 && k<alglib.ap.len(network.hllayersizes), "MLPGetLayerSize: incorrect layer index");
+            result = network.hllayersizes[k];
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function returns offset/scaling coefficients for I-th input of the
+        network.
+
+        INPUT PARAMETERS:
+            Network     -   network
+            I           -   input index
+
+        OUTPUT PARAMETERS:
+            Mean        -   mean term
+            Sigma       -   sigma term, guaranteed to be nonzero.
+
+        I-th input is passed through linear transformation
+            IN[i] = (IN[i]-Mean)/Sigma
+        before feeding to the network
+
+          -- ALGLIB --
+             Copyright 25.03.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpgetinputscaling(multilayerperceptron network,
+            int i,
+            ref double mean,
+            ref double sigma)
+        {
+            mean = 0;
+            sigma = 0;
+
+            alglib.ap.assert(i>=0 && i<network.hllayersizes[0], "MLPGetInputScaling: incorrect (nonexistent) I");
+            mean = network.columnmeans[i];
+            sigma = network.columnsigmas[i];
+            if( (double)(sigma)==(double)(0) )
+            {
+                sigma = 1;
+            }
+        }
+
+
+        /*************************************************************************
+        This function returns offset/scaling coefficients for I-th output of the
+        network.
+
+        INPUT PARAMETERS:
+            Network     -   network
+            I           -   input index
+
+        OUTPUT PARAMETERS:
+            Mean        -   mean term
+            Sigma       -   sigma term, guaranteed to be nonzero.
+
+        I-th output is passed through linear transformation
+            OUT[i] = OUT[i]*Sigma+Mean
+        before returning it to user. In case we have SOFTMAX-normalized network,
+        we return (Mean,Sigma)=(0.0,1.0).
+
+          -- ALGLIB --
+             Copyright 25.03.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpgetoutputscaling(multilayerperceptron network,
+            int i,
+            ref double mean,
+            ref double sigma)
+        {
+            mean = 0;
+            sigma = 0;
+
+            alglib.ap.assert(i>=0 && i<network.hllayersizes[alglib.ap.len(network.hllayersizes)-1], "MLPGetOutputScaling: incorrect (nonexistent) I");
+            if( network.structinfo[6]==1 )
+            {
+                mean = 0;
+                sigma = 1;
+            }
+            else
+            {
+                mean = network.columnmeans[network.hllayersizes[0]+i];
+                sigma = network.columnsigmas[network.hllayersizes[0]+i];
+            }
+        }
+
+
+        /*************************************************************************
+        This function returns information about Ith neuron of Kth layer
+
+        INPUT PARAMETERS:
+            Network     -   network
+            K           -   layer index
+            I           -   neuron index (within layer)
+
+        OUTPUT PARAMETERS:
+            FKind       -   activation function type (used by MLPActivationFunction())
+                            this value is zero for input or linear neurons
+            Threshold   -   also called offset, bias
+                            zero for input neurons
+                            
+        NOTE: this function throws exception if layer or neuron with  given  index
+        do not exists.
+
+          -- ALGLIB --
+             Copyright 25.03.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpgetneuroninfo(multilayerperceptron network,
+            int k,
+            int i,
+            ref int fkind,
+            ref double threshold)
+        {
+            int ncnt = 0;
+            int istart = 0;
+            int highlevelidx = 0;
+            int activationoffset = 0;
+
+            fkind = 0;
+            threshold = 0;
+
+            ncnt = alglib.ap.len(network.hlneurons)/hlnfieldwidth;
+            istart = network.structinfo[5];
+            
+            //
+            // search
+            //
+            network.integerbuf[0] = k;
+            network.integerbuf[1] = i;
+            highlevelidx = apserv.recsearch(ref network.hlneurons, hlnfieldwidth, 2, 0, ncnt, network.integerbuf);
+            alglib.ap.assert(highlevelidx>=0, "MLPGetNeuronInfo: incorrect (nonexistent) layer or neuron index");
+            
+            //
+            // 1. find offset of the activation function record in the
+            //
+            if( network.hlneurons[highlevelidx*hlnfieldwidth+2]>=0 )
+            {
+                activationoffset = istart+network.hlneurons[highlevelidx*hlnfieldwidth+2]*nfieldwidth;
+                fkind = network.structinfo[activationoffset+0];
+            }
+            else
+            {
+                fkind = 0;
+            }
+            if( network.hlneurons[highlevelidx*hlnfieldwidth+3]>=0 )
+            {
+                threshold = network.weights[network.hlneurons[highlevelidx*hlnfieldwidth+3]];
+            }
+            else
+            {
+                threshold = 0;
+            }
+        }
+
+
+        /*************************************************************************
+        This function returns information about connection from I0-th neuron of
+        K0-th layer to I1-th neuron of K1-th layer.
+
+        INPUT PARAMETERS:
+            Network     -   network
+            K0          -   layer index
+            I0          -   neuron index (within layer)
+            K1          -   layer index
+            I1          -   neuron index (within layer)
+
+        RESULT:
+            connection weight (zero for non-existent connections)
+
+        This function:
+        1. throws exception if layer or neuron with given index do not exists.
+        2. returns zero if neurons exist, but there is no connection between them
+
+          -- ALGLIB --
+             Copyright 25.03.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static double mlpgetweight(multilayerperceptron network,
+            int k0,
+            int i0,
+            int k1,
+            int i1)
+        {
+            double result = 0;
+            int ccnt = 0;
+            int highlevelidx = 0;
+
+            ccnt = alglib.ap.len(network.hlconnections)/hlconnfieldwidth;
+            
+            //
+            // check params
+            //
+            alglib.ap.assert(k0>=0 && k0<alglib.ap.len(network.hllayersizes), "MLPGetWeight: incorrect (nonexistent) K0");
+            alglib.ap.assert(i0>=0 && i0<network.hllayersizes[k0], "MLPGetWeight: incorrect (nonexistent) I0");
+            alglib.ap.assert(k1>=0 && k1<alglib.ap.len(network.hllayersizes), "MLPGetWeight: incorrect (nonexistent) K1");
+            alglib.ap.assert(i1>=0 && i1<network.hllayersizes[k1], "MLPGetWeight: incorrect (nonexistent) I1");
+            
+            //
+            // search
+            //
+            network.integerbuf[0] = k0;
+            network.integerbuf[1] = i0;
+            network.integerbuf[2] = k1;
+            network.integerbuf[3] = i1;
+            highlevelidx = apserv.recsearch(ref network.hlconnections, hlconnfieldwidth, 4, 0, ccnt, network.integerbuf);
+            if( highlevelidx>=0 )
+            {
+                result = network.weights[network.hlconnections[highlevelidx*hlconnfieldwidth+4]];
+            }
+            else
+            {
+                result = 0;
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function sets offset/scaling coefficients for I-th input of the
+        network.
+
+        INPUT PARAMETERS:
+            Network     -   network
+            I           -   input index
+            Mean        -   mean term
+            Sigma       -   sigma term (if zero, will be replaced by 1.0)
+
+        NTE: I-th input is passed through linear transformation
+            IN[i] = (IN[i]-Mean)/Sigma
+        before feeding to the network. This function sets Mean and Sigma.
+
+          -- ALGLIB --
+             Copyright 25.03.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpsetinputscaling(multilayerperceptron network,
+            int i,
+            double mean,
+            double sigma)
+        {
+            alglib.ap.assert(i>=0 && i<network.hllayersizes[0], "MLPSetInputScaling: incorrect (nonexistent) I");
+            alglib.ap.assert(math.isfinite(mean), "MLPSetInputScaling: infinite or NAN Mean");
+            alglib.ap.assert(math.isfinite(sigma), "MLPSetInputScaling: infinite or NAN Sigma");
+            if( (double)(sigma)==(double)(0) )
+            {
+                sigma = 1;
+            }
+            network.columnmeans[i] = mean;
+            network.columnsigmas[i] = sigma;
+        }
+
+
+        /*************************************************************************
+        This function sets offset/scaling coefficients for I-th output of the
+        network.
+
+        INPUT PARAMETERS:
+            Network     -   network
+            I           -   input index
+            Mean        -   mean term
+            Sigma       -   sigma term (if zero, will be replaced by 1.0)
+
+        OUTPUT PARAMETERS:
+
+        NOTE: I-th output is passed through linear transformation
+            OUT[i] = OUT[i]*Sigma+Mean
+        before returning it to user. This function sets Sigma/Mean. In case we
+        have SOFTMAX-normalized network, you can not set (Sigma,Mean) to anything
+        other than(0.0,1.0) - this function will throw exception.
+
+          -- ALGLIB --
+             Copyright 25.03.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpsetoutputscaling(multilayerperceptron network,
+            int i,
+            double mean,
+            double sigma)
+        {
+            alglib.ap.assert(i>=0 && i<network.hllayersizes[alglib.ap.len(network.hllayersizes)-1], "MLPSetOutputScaling: incorrect (nonexistent) I");
+            alglib.ap.assert(math.isfinite(mean), "MLPSetOutputScaling: infinite or NAN Mean");
+            alglib.ap.assert(math.isfinite(sigma), "MLPSetOutputScaling: infinite or NAN Sigma");
+            if( network.structinfo[6]==1 )
+            {
+                alglib.ap.assert((double)(mean)==(double)(0), "MLPSetOutputScaling: you can not set non-zero Mean term for classifier network");
+                alglib.ap.assert((double)(sigma)==(double)(1), "MLPSetOutputScaling: you can not set non-unit Sigma term for classifier network");
+            }
+            else
+            {
+                if( (double)(sigma)==(double)(0) )
+                {
+                    sigma = 1;
+                }
+                network.columnmeans[network.hllayersizes[0]+i] = mean;
+                network.columnsigmas[network.hllayersizes[0]+i] = sigma;
+            }
+        }
+
+
+        /*************************************************************************
+        This function modifies information about Ith neuron of Kth layer
+
+        INPUT PARAMETERS:
+            Network     -   network
+            K           -   layer index
+            I           -   neuron index (within layer)
+            FKind       -   activation function type (used by MLPActivationFunction())
+                            this value must be zero for input neurons
+                            (you can not set activation function for input neurons)
+            Threshold   -   also called offset, bias
+                            this value must be zero for input neurons
+                            (you can not set threshold for input neurons)
+
+        NOTES:
+        1. this function throws exception if layer or neuron with given index do
+           not exists.
+        2. this function also throws exception when you try to set non-linear
+           activation function for input neurons (any kind of network) or for output
+           neurons of classifier network.
+        3. this function throws exception when you try to set non-zero threshold for
+           input neurons (any kind of network).
+
+          -- ALGLIB --
+             Copyright 25.03.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpsetneuroninfo(multilayerperceptron network,
+            int k,
+            int i,
+            int fkind,
+            double threshold)
+        {
+            int ncnt = 0;
+            int istart = 0;
+            int highlevelidx = 0;
+            int activationoffset = 0;
+
+            alglib.ap.assert(math.isfinite(threshold), "MLPSetNeuronInfo: infinite or NAN Threshold");
+            
+            //
+            // convenience vars
+            //
+            ncnt = alglib.ap.len(network.hlneurons)/hlnfieldwidth;
+            istart = network.structinfo[5];
+            
+            //
+            // search
+            //
+            network.integerbuf[0] = k;
+            network.integerbuf[1] = i;
+            highlevelidx = apserv.recsearch(ref network.hlneurons, hlnfieldwidth, 2, 0, ncnt, network.integerbuf);
+            alglib.ap.assert(highlevelidx>=0, "MLPSetNeuronInfo: incorrect (nonexistent) layer or neuron index");
+            
+            //
+            // activation function
+            //
+            if( network.hlneurons[highlevelidx*hlnfieldwidth+2]>=0 )
+            {
+                activationoffset = istart+network.hlneurons[highlevelidx*hlnfieldwidth+2]*nfieldwidth;
+                network.structinfo[activationoffset+0] = fkind;
+            }
+            else
+            {
+                alglib.ap.assert(fkind==0, "MLPSetNeuronInfo: you try to set activation function for neuron which can not have one");
+            }
+            
+            //
+            // Threshold
+            //
+            if( network.hlneurons[highlevelidx*hlnfieldwidth+3]>=0 )
+            {
+                network.weights[network.hlneurons[highlevelidx*hlnfieldwidth+3]] = threshold;
+            }
+            else
+            {
+                alglib.ap.assert((double)(threshold)==(double)(0), "MLPSetNeuronInfo: you try to set non-zero threshold for neuron which can not have one");
+            }
+        }
+
+
+        /*************************************************************************
+        This function modifies information about connection from I0-th neuron of
+        K0-th layer to I1-th neuron of K1-th layer.
+
+        INPUT PARAMETERS:
+            Network     -   network
+            K0          -   layer index
+            I0          -   neuron index (within layer)
+            K1          -   layer index
+            I1          -   neuron index (within layer)
+            W           -   connection weight (must be zero for non-existent
+                            connections)
+
+        This function:
+        1. throws exception if layer or neuron with given index do not exists.
+        2. throws exception if you try to set non-zero weight for non-existent
+           connection
+
+          -- ALGLIB --
+             Copyright 25.03.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpsetweight(multilayerperceptron network,
+            int k0,
+            int i0,
+            int k1,
+            int i1,
+            double w)
+        {
+            int ccnt = 0;
+            int highlevelidx = 0;
+
+            ccnt = alglib.ap.len(network.hlconnections)/hlconnfieldwidth;
+            
+            //
+            // check params
+            //
+            alglib.ap.assert(k0>=0 && k0<alglib.ap.len(network.hllayersizes), "MLPSetWeight: incorrect (nonexistent) K0");
+            alglib.ap.assert(i0>=0 && i0<network.hllayersizes[k0], "MLPSetWeight: incorrect (nonexistent) I0");
+            alglib.ap.assert(k1>=0 && k1<alglib.ap.len(network.hllayersizes), "MLPSetWeight: incorrect (nonexistent) K1");
+            alglib.ap.assert(i1>=0 && i1<network.hllayersizes[k1], "MLPSetWeight: incorrect (nonexistent) I1");
+            alglib.ap.assert(math.isfinite(w), "MLPSetWeight: infinite or NAN weight");
+            
+            //
+            // search
+            //
+            network.integerbuf[0] = k0;
+            network.integerbuf[1] = i0;
+            network.integerbuf[2] = k1;
+            network.integerbuf[3] = i1;
+            highlevelidx = apserv.recsearch(ref network.hlconnections, hlconnfieldwidth, 4, 0, ccnt, network.integerbuf);
+            if( highlevelidx>=0 )
+            {
+                network.weights[network.hlconnections[highlevelidx*hlconnfieldwidth+4]] = w;
+            }
+            else
+            {
+                alglib.ap.assert((double)(w)==(double)(0), "MLPSetWeight: you try to set non-zero weight for non-existent connection");
+            }
+        }
+
+
+        /*************************************************************************
+        Neural network activation function
+
+        INPUT PARAMETERS:
+            NET         -   neuron input
+            K           -   function index (zero for linear function)
+
+        OUTPUT PARAMETERS:
+            F           -   function
+            DF          -   its derivative
+            D2F         -   its second derivative
+
+          -- ALGLIB --
+             Copyright 04.11.2007 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpactivationfunction(double net,
+            int k,
+            ref double f,
+            ref double df,
+            ref double d2f)
+        {
+            double net2 = 0;
+            double arg = 0;
+            double root = 0;
+            double r = 0;
+
+            f = 0;
+            df = 0;
+            d2f = 0;
+
+            if( k==0 || k==-5 )
+            {
+                f = net;
+                df = 1;
+                d2f = 0;
+                return;
+            }
+            if( k==1 )
+            {
+                
+                //
+                // TanH activation function
+                //
+                if( (double)(Math.Abs(net))<(double)(100) )
+                {
+                    f = Math.Tanh(net);
+                }
+                else
+                {
+                    f = Math.Sign(net);
+                }
+                df = 1-math.sqr(f);
+                d2f = -(2*f*df);
+                return;
+            }
+            if( k==3 )
+            {
+                
+                //
+                // EX activation function
+                //
+                if( (double)(net)>=(double)(0) )
+                {
+                    net2 = net*net;
+                    arg = net2+1;
+                    root = Math.Sqrt(arg);
+                    f = net+root;
+                    r = net/root;
+                    df = 1+r;
+                    d2f = (root-net*r)/arg;
+                }
+                else
+                {
+                    f = Math.Exp(net);
+                    df = f;
+                    d2f = f;
+                }
+                return;
+            }
+            if( k==2 )
+            {
+                f = Math.Exp(-math.sqr(net));
+                df = -(2*net*f);
+                d2f = -(2*(f+df*net));
+                return;
+            }
+            f = 0;
+            df = 0;
+            d2f = 0;
+        }
+
+
+        /*************************************************************************
+        Procesing
+
+        INPUT PARAMETERS:
+            Network -   neural network
+            X       -   input vector,  array[0..NIn-1].
+
+        OUTPUT PARAMETERS:
+            Y       -   result. Regression estimate when solving regression  task,
+                        vector of posterior probabilities for classification task.
+
+        See also MLPProcessI
+
+          -- ALGLIB --
+             Copyright 04.11.2007 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpprocess(multilayerperceptron network,
+            double[] x,
+            ref double[] y)
+        {
+            if( alglib.ap.len(y)<network.structinfo[2] )
+            {
+                y = new double[network.structinfo[2]];
+            }
+            mlpinternalprocessvector(network.structinfo, network.weights, network.columnmeans, network.columnsigmas, ref network.neurons, ref network.dfdnet, x, ref y);
+        }
+
+
+        /*************************************************************************
+        'interactive'  variant  of  MLPProcess  for  languages  like  Python which
+        support constructs like "Y = MLPProcess(NN,X)" and interactive mode of the
+        interpreter
+
+        This function allocates new array on each call,  so  it  is  significantly
+        slower than its 'non-interactive' counterpart, but it is  more  convenient
+        when you call it from command line.
+
+          -- ALGLIB --
+             Copyright 21.09.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpprocessi(multilayerperceptron network,
+            double[] x,
+            ref double[] y)
+        {
+            y = new double[0];
+
+            mlpprocess(network, x, ref y);
+        }
+
+
+        /*************************************************************************
+        Error function for neural network, internal subroutine.
+
+          -- ALGLIB --
+             Copyright 04.11.2007 by Bochkanov Sergey
+        *************************************************************************/
+        public static double mlperror(multilayerperceptron network,
+            double[,] xy,
+            int ssize)
+        {
+            double result = 0;
+            int i = 0;
+            int k = 0;
+            int nin = 0;
+            int nout = 0;
+            int wcount = 0;
+            double e = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            mlpproperties(network, ref nin, ref nout, ref wcount);
+            result = 0;
+            for(i=0; i<=ssize-1; i++)
+            {
+                for(i_=0; i_<=nin-1;i_++)
+                {
+                    network.x[i_] = xy[i,i_];
+                }
+                mlpprocess(network, network.x, ref network.y);
+                if( mlpissoftmax(network) )
+                {
+                    
+                    //
+                    // class labels outputs
+                    //
+                    k = (int)Math.Round(xy[i,nin]);
+                    if( k>=0 && k<nout )
+                    {
+                        network.y[k] = network.y[k]-1;
+                    }
+                }
+                else
+                {
+                    
+                    //
+                    // real outputs
+                    //
+                    i1_ = (nin) - (0);
+                    for(i_=0; i_<=nout-1;i_++)
+                    {
+                        network.y[i_] = network.y[i_] - xy[i,i_+i1_];
+                    }
+                }
+                e = 0.0;
+                for(i_=0; i_<=nout-1;i_++)
+                {
+                    e += network.y[i_]*network.y[i_];
+                }
+                result = result+e/2;
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        Natural error function for neural network, internal subroutine.
+
+          -- ALGLIB --
+             Copyright 04.11.2007 by Bochkanov Sergey
+        *************************************************************************/
+        public static double mlperrorn(multilayerperceptron network,
+            double[,] xy,
+            int ssize)
+        {
+            double result = 0;
+            int i = 0;
+            int k = 0;
+            int nin = 0;
+            int nout = 0;
+            int wcount = 0;
+            double e = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            mlpproperties(network, ref nin, ref nout, ref wcount);
+            result = 0;
+            for(i=0; i<=ssize-1; i++)
+            {
+                
+                //
+                // Process vector
+                //
+                for(i_=0; i_<=nin-1;i_++)
+                {
+                    network.x[i_] = xy[i,i_];
+                }
+                mlpprocess(network, network.x, ref network.y);
+                
+                //
+                // Update error function
+                //
+                if( network.structinfo[6]==0 )
+                {
+                    
+                    //
+                    // Least squares error function
+                    //
+                    i1_ = (nin) - (0);
+                    for(i_=0; i_<=nout-1;i_++)
+                    {
+                        network.y[i_] = network.y[i_] - xy[i,i_+i1_];
+                    }
+                    e = 0.0;
+                    for(i_=0; i_<=nout-1;i_++)
+                    {
+                        e += network.y[i_]*network.y[i_];
+                    }
+                    result = result+e/2;
+                }
+                else
+                {
+                    
+                    //
+                    // Cross-entropy error function
+                    //
+                    k = (int)Math.Round(xy[i,nin]);
+                    if( k>=0 && k<nout )
+                    {
+                        result = result+safecrossentropy(1, network.y[k]);
+                    }
+                }
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        Classification error
+
+          -- ALGLIB --
+             Copyright 04.11.2007 by Bochkanov Sergey
+        *************************************************************************/
+        public static int mlpclserror(multilayerperceptron network,
+            double[,] xy,
+            int ssize)
+        {
+            int result = 0;
+            int i = 0;
+            int j = 0;
+            int nin = 0;
+            int nout = 0;
+            int wcount = 0;
+            double[] workx = new double[0];
+            double[] worky = new double[0];
+            int nn = 0;
+            int ns = 0;
+            int nmax = 0;
+            int i_ = 0;
+
+            mlpproperties(network, ref nin, ref nout, ref wcount);
+            workx = new double[nin-1+1];
+            worky = new double[nout-1+1];
+            result = 0;
+            for(i=0; i<=ssize-1; i++)
+            {
+                
+                //
+                // Process
+                //
+                for(i_=0; i_<=nin-1;i_++)
+                {
+                    workx[i_] = xy[i,i_];
+                }
+                mlpprocess(network, workx, ref worky);
+                
+                //
+                // Network version of the answer
+                //
+                nmax = 0;
+                for(j=0; j<=nout-1; j++)
+                {
+                    if( (double)(worky[j])>(double)(worky[nmax]) )
+                    {
+                        nmax = j;
+                    }
+                }
+                nn = nmax;
+                
+                //
+                // Right answer
+                //
+                if( mlpissoftmax(network) )
+                {
+                    ns = (int)Math.Round(xy[i,nin]);
+                }
+                else
+                {
+                    nmax = 0;
+                    for(j=0; j<=nout-1; j++)
+                    {
+                        if( (double)(xy[i,nin+j])>(double)(xy[i,nin+nmax]) )
+                        {
+                            nmax = j;
+                        }
+                    }
+                    ns = nmax;
+                }
+                
+                //
+                // compare
+                //
+                if( nn!=ns )
+                {
+                    result = result+1;
+                }
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        Relative classification error on the test set
+
+        INPUT PARAMETERS:
+            Network -   network
+            XY      -   test set
+            NPoints -   test set size
+
+        RESULT:
+            percent of incorrectly classified cases. Works both for
+            classifier networks and general purpose networks used as
+            classifiers.
+
+          -- ALGLIB --
+             Copyright 25.12.2008 by Bochkanov Sergey
+        *************************************************************************/
+        public static double mlprelclserror(multilayerperceptron network,
+            double[,] xy,
+            int npoints)
+        {
+            double result = 0;
+
+            result = (double)mlpclserror(network, xy, npoints)/(double)npoints;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Average cross-entropy (in bits per element) on the test set
+
+        INPUT PARAMETERS:
+            Network -   neural network
+            XY      -   test set
+            NPoints -   test set size
+
+        RESULT:
+            CrossEntropy/(NPoints*LN(2)).
+            Zero if network solves regression task.
+
+          -- ALGLIB --
+             Copyright 08.01.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static double mlpavgce(multilayerperceptron network,
+            double[,] xy,
+            int npoints)
+        {
+            double result = 0;
+            int nin = 0;
+            int nout = 0;
+            int wcount = 0;
+
+            if( mlpissoftmax(network) )
+            {
+                mlpproperties(network, ref nin, ref nout, ref wcount);
+                result = mlperrorn(network, xy, npoints)/(npoints*Math.Log(2));
+            }
+            else
+            {
+                result = 0;
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        RMS error on the test set
+
+        INPUT PARAMETERS:
+            Network -   neural network
+            XY      -   test set
+            NPoints -   test set size
+
+        RESULT:
+            root mean square error.
+            Its meaning for regression task is obvious. As for
+            classification task, RMS error means error when estimating posterior
+            probabilities.
+
+          -- ALGLIB --
+             Copyright 04.11.2007 by Bochkanov Sergey
+        *************************************************************************/
+        public static double mlprmserror(multilayerperceptron network,
+            double[,] xy,
+            int npoints)
+        {
+            double result = 0;
+            int nin = 0;
+            int nout = 0;
+            int wcount = 0;
+
+            mlpproperties(network, ref nin, ref nout, ref wcount);
+            result = Math.Sqrt(2*mlperror(network, xy, npoints)/(npoints*nout));
+            return result;
+        }
+
+
+        /*************************************************************************
+        Average error on the test set
+
+        INPUT PARAMETERS:
+            Network -   neural network
+            XY      -   test set
+            NPoints -   test set size
+
+        RESULT:
+            Its meaning for regression task is obvious. As for
+            classification task, it means average error when estimating posterior
+            probabilities.
+
+          -- ALGLIB --
+             Copyright 11.03.2008 by Bochkanov Sergey
+        *************************************************************************/
+        public static double mlpavgerror(multilayerperceptron network,
+            double[,] xy,
+            int npoints)
+        {
+            double result = 0;
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int nin = 0;
+            int nout = 0;
+            int wcount = 0;
+            int i_ = 0;
+
+            mlpproperties(network, ref nin, ref nout, ref wcount);
+            result = 0;
+            for(i=0; i<=npoints-1; i++)
+            {
+                for(i_=0; i_<=nin-1;i_++)
+                {
+                    network.x[i_] = xy[i,i_];
+                }
+                mlpprocess(network, network.x, ref network.y);
+                if( mlpissoftmax(network) )
+                {
+                    
+                    //
+                    // class labels
+                    //
+                    k = (int)Math.Round(xy[i,nin]);
+                    for(j=0; j<=nout-1; j++)
+                    {
+                        if( j==k )
+                        {
+                            result = result+Math.Abs(1-network.y[j]);
+                        }
+                        else
+                        {
+                            result = result+Math.Abs(network.y[j]);
+                        }
+                    }
+                }
+                else
+                {
+                    
+                    //
+                    // real outputs
+                    //
+                    for(j=0; j<=nout-1; j++)
+                    {
+                        result = result+Math.Abs(xy[i,nin+j]-network.y[j]);
+                    }
+                }
+            }
+            result = result/(npoints*nout);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Average relative error on the test set
+
+        INPUT PARAMETERS:
+            Network -   neural network
+            XY      -   test set
+            NPoints -   test set size
+
+        RESULT:
+            Its meaning for regression task is obvious. As for
+            classification task, it means average relative error when estimating
+            posterior probability of belonging to the correct class.
+
+          -- ALGLIB --
+             Copyright 11.03.2008 by Bochkanov Sergey
+        *************************************************************************/
+        public static double mlpavgrelerror(multilayerperceptron network,
+            double[,] xy,
+            int npoints)
+        {
+            double result = 0;
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int lk = 0;
+            int nin = 0;
+            int nout = 0;
+            int wcount = 0;
+            int i_ = 0;
+
+            mlpproperties(network, ref nin, ref nout, ref wcount);
+            result = 0;
+            k = 0;
+            for(i=0; i<=npoints-1; i++)
+            {
+                for(i_=0; i_<=nin-1;i_++)
+                {
+                    network.x[i_] = xy[i,i_];
+                }
+                mlpprocess(network, network.x, ref network.y);
+                if( mlpissoftmax(network) )
+                {
+                    
+                    //
+                    // class labels
+                    //
+                    lk = (int)Math.Round(xy[i,nin]);
+                    for(j=0; j<=nout-1; j++)
+                    {
+                        if( j==lk )
+                        {
+                            result = result+Math.Abs(1-network.y[j]);
+                            k = k+1;
+                        }
+                    }
+                }
+                else
+                {
+                    
+                    //
+                    // real outputs
+                    //
+                    for(j=0; j<=nout-1; j++)
+                    {
+                        if( (double)(xy[i,nin+j])!=(double)(0) )
+                        {
+                            result = result+Math.Abs(xy[i,nin+j]-network.y[j])/Math.Abs(xy[i,nin+j]);
+                            k = k+1;
+                        }
+                    }
+                }
+            }
+            if( k!=0 )
+            {
+                result = result/k;
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        Gradient calculation
+
+        INPUT PARAMETERS:
+            Network -   network initialized with one of the network creation funcs
+            X       -   input vector, length of array must be at least NIn
+            DesiredY-   desired outputs, length of array must be at least NOut
+            Grad    -   possibly preallocated array. If size of array is smaller
+                        than WCount, it will be reallocated. It is recommended to
+                        reuse previously allocated array to reduce allocation
+                        overhead.
+
+        OUTPUT PARAMETERS:
+            E       -   error function, SUM(sqr(y[i]-desiredy[i])/2,i)
+            Grad    -   gradient of E with respect to weights of network, array[WCount]
+            
+          -- ALGLIB --
+             Copyright 04.11.2007 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpgrad(multilayerperceptron network,
+            double[] x,
+            double[] desiredy,
+            ref double e,
+            ref double[] grad)
+        {
+            int i = 0;
+            int nout = 0;
+            int ntotal = 0;
+
+            e = 0;
+
+            
+            //
+            // Alloc
+            //
+            if( alglib.ap.len(grad)<network.structinfo[4] )
+            {
+                grad = new double[network.structinfo[4]];
+            }
+            
+            //
+            // Prepare dError/dOut, internal structures
+            //
+            mlpprocess(network, x, ref network.y);
+            nout = network.structinfo[2];
+            ntotal = network.structinfo[3];
+            e = 0;
+            for(i=0; i<=ntotal-1; i++)
+            {
+                network.derror[i] = 0;
+            }
+            for(i=0; i<=nout-1; i++)
+            {
+                network.derror[ntotal-nout+i] = network.y[i]-desiredy[i];
+                e = e+math.sqr(network.y[i]-desiredy[i])/2;
+            }
+            
+            //
+            // gradient
+            //
+            mlpinternalcalculategradient(network, network.neurons, network.weights, ref network.derror, ref grad, false);
+        }
+
+
+        /*************************************************************************
+        Gradient calculation (natural error function is used)
+
+        INPUT PARAMETERS:
+            Network -   network initialized with one of the network creation funcs
+            X       -   input vector, length of array must be at least NIn
+            DesiredY-   desired outputs, length of array must be at least NOut
+            Grad    -   possibly preallocated array. If size of array is smaller
+                        than WCount, it will be reallocated. It is recommended to
+                        reuse previously allocated array to reduce allocation
+                        overhead.
+
+        OUTPUT PARAMETERS:
+            E       -   error function, sum-of-squares for regression networks,
+                        cross-entropy for classification networks.
+            Grad    -   gradient of E with respect to weights of network, array[WCount]
+
+          -- ALGLIB --
+             Copyright 04.11.2007 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpgradn(multilayerperceptron network,
+            double[] x,
+            double[] desiredy,
+            ref double e,
+            ref double[] grad)
+        {
+            double s = 0;
+            int i = 0;
+            int nout = 0;
+            int ntotal = 0;
+
+            e = 0;
+
+            
+            //
+            // Alloc
+            //
+            if( alglib.ap.len(grad)<network.structinfo[4] )
+            {
+                grad = new double[network.structinfo[4]];
+            }
+            
+            //
+            // Prepare dError/dOut, internal structures
+            //
+            mlpprocess(network, x, ref network.y);
+            nout = network.structinfo[2];
+            ntotal = network.structinfo[3];
+            for(i=0; i<=ntotal-1; i++)
+            {
+                network.derror[i] = 0;
+            }
+            e = 0;
+            if( network.structinfo[6]==0 )
+            {
+                
+                //
+                // Regression network, least squares
+                //
+                for(i=0; i<=nout-1; i++)
+                {
+                    network.derror[ntotal-nout+i] = network.y[i]-desiredy[i];
+                    e = e+math.sqr(network.y[i]-desiredy[i])/2;
+                }
+            }
+            else
+            {
+                
+                //
+                // Classification network, cross-entropy
+                //
+                s = 0;
+                for(i=0; i<=nout-1; i++)
+                {
+                    s = s+desiredy[i];
+                }
+                for(i=0; i<=nout-1; i++)
+                {
+                    network.derror[ntotal-nout+i] = s*network.y[i]-desiredy[i];
+                    e = e+safecrossentropy(desiredy[i], network.y[i]);
+                }
+            }
+            
+            //
+            // gradient
+            //
+            mlpinternalcalculategradient(network, network.neurons, network.weights, ref network.derror, ref grad, true);
+        }
+
+
+        /*************************************************************************
+        Batch gradient calculation for a set of inputs/outputs
+
+        INPUT PARAMETERS:
+            Network -   network initialized with one of the network creation funcs
+            XY      -   set of inputs/outputs; one sample = one row;
+                        first NIn columns contain inputs,
+                        next NOut columns - desired outputs.
+            SSize   -   number of elements in XY
+            Grad    -   possibly preallocated array. If size of array is smaller
+                        than WCount, it will be reallocated. It is recommended to
+                        reuse previously allocated array to reduce allocation
+                        overhead.
+
+        OUTPUT PARAMETERS:
+            E       -   error function, SUM(sqr(y[i]-desiredy[i])/2,i)
+            Grad    -   gradient of E with respect to weights of network, array[WCount]
+
+          -- ALGLIB --
+             Copyright 04.11.2007 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpgradbatch(multilayerperceptron network,
+            double[,] xy,
+            int ssize,
+            ref double e,
+            ref double[] grad)
+        {
+            int i = 0;
+            int nin = 0;
+            int nout = 0;
+            int wcount = 0;
+
+            e = 0;
+
+            mlpproperties(network, ref nin, ref nout, ref wcount);
+            for(i=0; i<=wcount-1; i++)
+            {
+                grad[i] = 0;
+            }
+            e = 0;
+            i = 0;
+            while( i<=ssize-1 )
+            {
+                mlpchunkedgradient(network, xy, i, Math.Min(ssize, i+chunksize)-i, ref e, ref grad, false);
+                i = i+chunksize;
+            }
+        }
+
+
+        /*************************************************************************
+        Batch gradient calculation for a set of inputs/outputs
+        (natural error function is used)
+
+        INPUT PARAMETERS:
+            Network -   network initialized with one of the network creation funcs
+            XY      -   set of inputs/outputs; one sample = one row;
+                        first NIn columns contain inputs,
+                        next NOut columns - desired outputs.
+            SSize   -   number of elements in XY
+            Grad    -   possibly preallocated array. If size of array is smaller
+                        than WCount, it will be reallocated. It is recommended to
+                        reuse previously allocated array to reduce allocation
+                        overhead.
+
+        OUTPUT PARAMETERS:
+            E       -   error function, sum-of-squares for regression networks,
+                        cross-entropy for classification networks.
+            Grad    -   gradient of E with respect to weights of network, array[WCount]
+
+          -- ALGLIB --
+             Copyright 04.11.2007 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpgradnbatch(multilayerperceptron network,
+            double[,] xy,
+            int ssize,
+            ref double e,
+            ref double[] grad)
+        {
+            int i = 0;
+            int nin = 0;
+            int nout = 0;
+            int wcount = 0;
+
+            e = 0;
+
+            mlpproperties(network, ref nin, ref nout, ref wcount);
+            for(i=0; i<=wcount-1; i++)
+            {
+                grad[i] = 0;
+            }
+            e = 0;
+            i = 0;
+            while( i<=ssize-1 )
+            {
+                mlpchunkedgradient(network, xy, i, Math.Min(ssize, i+chunksize)-i, ref e, ref grad, true);
+                i = i+chunksize;
+            }
+        }
+
+
+        /*************************************************************************
+        Batch Hessian calculation (natural error function) using R-algorithm.
+        Internal subroutine.
+
+          -- ALGLIB --
+             Copyright 26.01.2008 by Bochkanov Sergey.
+             
+             Hessian calculation based on R-algorithm described in
+             "Fast Exact Multiplication by the Hessian",
+             B. A. Pearlmutter,
+             Neural Computation, 1994.
+        *************************************************************************/
+        public static void mlphessiannbatch(multilayerperceptron network,
+            double[,] xy,
+            int ssize,
+            ref double e,
+            ref double[] grad,
+            ref double[,] h)
+        {
+            e = 0;
+
+            mlphessianbatchinternal(network, xy, ssize, true, ref e, ref grad, ref h);
+        }
+
+
+        /*************************************************************************
+        Batch Hessian calculation using R-algorithm.
+        Internal subroutine.
+
+          -- ALGLIB --
+             Copyright 26.01.2008 by Bochkanov Sergey.
+
+             Hessian calculation based on R-algorithm described in
+             "Fast Exact Multiplication by the Hessian",
+             B. A. Pearlmutter,
+             Neural Computation, 1994.
+        *************************************************************************/
+        public static void mlphessianbatch(multilayerperceptron network,
+            double[,] xy,
+            int ssize,
+            ref double e,
+            ref double[] grad,
+            ref double[,] h)
+        {
+            e = 0;
+
+            mlphessianbatchinternal(network, xy, ssize, false, ref e, ref grad, ref h);
+        }
+
+
+        /*************************************************************************
+        Internal subroutine, shouldn't be called by user.
+        *************************************************************************/
+        public static void mlpinternalprocessvector(int[] structinfo,
+            double[] weights,
+            double[] columnmeans,
+            double[] columnsigmas,
+            ref double[] neurons,
+            ref double[] dfdnet,
+            double[] x,
+            ref double[] y)
+        {
+            int i = 0;
+            int n1 = 0;
+            int n2 = 0;
+            int w1 = 0;
+            int w2 = 0;
+            int ntotal = 0;
+            int nin = 0;
+            int nout = 0;
+            int istart = 0;
+            int offs = 0;
+            double net = 0;
+            double f = 0;
+            double df = 0;
+            double d2f = 0;
+            double mx = 0;
+            bool perr = new bool();
+            int i_ = 0;
+            int i1_ = 0;
+
+            
+            //
+            // Read network geometry
+            //
+            nin = structinfo[1];
+            nout = structinfo[2];
+            ntotal = structinfo[3];
+            istart = structinfo[5];
+            
+            //
+            // Inputs standartisation and putting in the network
+            //
+            for(i=0; i<=nin-1; i++)
+            {
+                if( (double)(columnsigmas[i])!=(double)(0) )
+                {
+                    neurons[i] = (x[i]-columnmeans[i])/columnsigmas[i];
+                }
+                else
+                {
+                    neurons[i] = x[i]-columnmeans[i];
+                }
+            }
+            
+            //
+            // Process network
+            //
+            for(i=0; i<=ntotal-1; i++)
+            {
+                offs = istart+i*nfieldwidth;
+                if( structinfo[offs+0]>0 || structinfo[offs+0]==-5 )
+                {
+                    
+                    //
+                    // Activation function
+                    //
+                    mlpactivationfunction(neurons[structinfo[offs+2]], structinfo[offs+0], ref f, ref df, ref d2f);
+                    neurons[i] = f;
+                    dfdnet[i] = df;
+                    continue;
+                }
+                if( structinfo[offs+0]==0 )
+                {
+                    
+                    //
+                    // Adaptive summator
+                    //
+                    n1 = structinfo[offs+2];
+                    n2 = n1+structinfo[offs+1]-1;
+                    w1 = structinfo[offs+3];
+                    w2 = w1+structinfo[offs+1]-1;
+                    i1_ = (n1)-(w1);
+                    net = 0.0;
+                    for(i_=w1; i_<=w2;i_++)
+                    {
+                        net += weights[i_]*neurons[i_+i1_];
+                    }
+                    neurons[i] = net;
+                    dfdnet[i] = 1.0;
+                    continue;
+                }
+                if( structinfo[offs+0]<0 )
+                {
+                    perr = true;
+                    if( structinfo[offs+0]==-2 )
+                    {
+                        
+                        //
+                        // input neuron, left unchanged
+                        //
+                        perr = false;
+                    }
+                    if( structinfo[offs+0]==-3 )
+                    {
+                        
+                        //
+                        // "-1" neuron
+                        //
+                        neurons[i] = -1;
+                        perr = false;
+                    }
+                    if( structinfo[offs+0]==-4 )
+                    {
+                        
+                        //
+                        // "0" neuron
+                        //
+                        neurons[i] = 0;
+                        perr = false;
+                    }
+                    alglib.ap.assert(!perr, "MLPInternalProcessVector: internal error - unknown neuron type!");
+                    continue;
+                }
+            }
+            
+            //
+            // Extract result
+            //
+            i1_ = (ntotal-nout) - (0);
+            for(i_=0; i_<=nout-1;i_++)
+            {
+                y[i_] = neurons[i_+i1_];
+            }
+            
+            //
+            // Softmax post-processing or standardisation if needed
+            //
+            alglib.ap.assert(structinfo[6]==0 || structinfo[6]==1, "MLPInternalProcessVector: unknown normalization type!");
+            if( structinfo[6]==1 )
+            {
+                
+                //
+                // Softmax
+                //
+                mx = y[0];
+                for(i=1; i<=nout-1; i++)
+                {
+                    mx = Math.Max(mx, y[i]);
+                }
+                net = 0;
+                for(i=0; i<=nout-1; i++)
+                {
+                    y[i] = Math.Exp(y[i]-mx);
+                    net = net+y[i];
+                }
+                for(i=0; i<=nout-1; i++)
+                {
+                    y[i] = y[i]/net;
+                }
+            }
+            else
+            {
+                
+                //
+                // Standardisation
+                //
+                for(i=0; i<=nout-1; i++)
+                {
+                    y[i] = y[i]*columnsigmas[nin+i]+columnmeans[nin+i];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Serializer: allocation
+
+          -- ALGLIB --
+             Copyright 14.03.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpalloc(alglib.serializer s,
+            multilayerperceptron network)
+        {
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int fkind = 0;
+            double threshold = 0;
+            double v0 = 0;
+            double v1 = 0;
+            int nin = 0;
+            int nout = 0;
+
+            nin = network.hllayersizes[0];
+            nout = network.hllayersizes[alglib.ap.len(network.hllayersizes)-1];
+            s.alloc_entry();
+            s.alloc_entry();
+            s.alloc_entry();
+            apserv.allocintegerarray(s, network.hllayersizes, -1);
+            for(i=1; i<=alglib.ap.len(network.hllayersizes)-1; i++)
+            {
+                for(j=0; j<=network.hllayersizes[i]-1; j++)
+                {
+                    mlpgetneuroninfo(network, i, j, ref fkind, ref threshold);
+                    s.alloc_entry();
+                    s.alloc_entry();
+                    for(k=0; k<=network.hllayersizes[i-1]-1; k++)
+                    {
+                        s.alloc_entry();
+                    }
+                }
+            }
+            for(j=0; j<=nin-1; j++)
+            {
+                mlpgetinputscaling(network, j, ref v0, ref v1);
+                s.alloc_entry();
+                s.alloc_entry();
+            }
+            for(j=0; j<=nout-1; j++)
+            {
+                mlpgetoutputscaling(network, j, ref v0, ref v1);
+                s.alloc_entry();
+                s.alloc_entry();
+            }
+        }
+
+
+        /*************************************************************************
+        Serializer: serialization
+
+          -- ALGLIB --
+             Copyright 14.03.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpserialize(alglib.serializer s,
+            multilayerperceptron network)
+        {
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int fkind = 0;
+            double threshold = 0;
+            double v0 = 0;
+            double v1 = 0;
+            int nin = 0;
+            int nout = 0;
+
+            nin = network.hllayersizes[0];
+            nout = network.hllayersizes[alglib.ap.len(network.hllayersizes)-1];
+            s.serialize_int(scodes.getmlpserializationcode());
+            s.serialize_int(mlpfirstversion);
+            s.serialize_bool(mlpissoftmax(network));
+            apserv.serializeintegerarray(s, network.hllayersizes, -1);
+            for(i=1; i<=alglib.ap.len(network.hllayersizes)-1; i++)
+            {
+                for(j=0; j<=network.hllayersizes[i]-1; j++)
+                {
+                    mlpgetneuroninfo(network, i, j, ref fkind, ref threshold);
+                    s.serialize_int(fkind);
+                    s.serialize_double(threshold);
+                    for(k=0; k<=network.hllayersizes[i-1]-1; k++)
+                    {
+                        s.serialize_double(mlpgetweight(network, i-1, k, i, j));
+                    }
+                }
+            }
+            for(j=0; j<=nin-1; j++)
+            {
+                mlpgetinputscaling(network, j, ref v0, ref v1);
+                s.serialize_double(v0);
+                s.serialize_double(v1);
+            }
+            for(j=0; j<=nout-1; j++)
+            {
+                mlpgetoutputscaling(network, j, ref v0, ref v1);
+                s.serialize_double(v0);
+                s.serialize_double(v1);
+            }
+        }
+
+
+        /*************************************************************************
+        Serializer: unserialization
+
+          -- ALGLIB --
+             Copyright 14.03.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpunserialize(alglib.serializer s,
+            multilayerperceptron network)
+        {
+            int i0 = 0;
+            int i1 = 0;
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int fkind = 0;
+            double threshold = 0;
+            double v0 = 0;
+            double v1 = 0;
+            int nin = 0;
+            int nout = 0;
+            bool issoftmax = new bool();
+            int[] layersizes = new int[0];
+
+            
+            //
+            // check correctness of header
+            //
+            i0 = s.unserialize_int();
+            alglib.ap.assert(i0==scodes.getmlpserializationcode(), "MLPUnserialize: stream header corrupted");
+            i1 = s.unserialize_int();
+            alglib.ap.assert(i1==mlpfirstversion, "MLPUnserialize: stream header corrupted");
+            
+            //
+            // Create network
+            //
+            issoftmax = s.unserialize_bool();
+            apserv.unserializeintegerarray(s, ref layersizes);
+            alglib.ap.assert((alglib.ap.len(layersizes)==2 || alglib.ap.len(layersizes)==3) || alglib.ap.len(layersizes)==4, "MLPUnserialize: too many hidden layers!");
+            nin = layersizes[0];
+            nout = layersizes[alglib.ap.len(layersizes)-1];
+            if( alglib.ap.len(layersizes)==2 )
+            {
+                if( issoftmax )
+                {
+                    mlpcreatec0(layersizes[0], layersizes[1], network);
+                }
+                else
+                {
+                    mlpcreate0(layersizes[0], layersizes[1], network);
+                }
+            }
+            if( alglib.ap.len(layersizes)==3 )
+            {
+                if( issoftmax )
+                {
+                    mlpcreatec1(layersizes[0], layersizes[1], layersizes[2], network);
+                }
+                else
+                {
+                    mlpcreate1(layersizes[0], layersizes[1], layersizes[2], network);
+                }
+            }
+            if( alglib.ap.len(layersizes)==4 )
+            {
+                if( issoftmax )
+                {
+                    mlpcreatec2(layersizes[0], layersizes[1], layersizes[2], layersizes[3], network);
+                }
+                else
+                {
+                    mlpcreate2(layersizes[0], layersizes[1], layersizes[2], layersizes[3], network);
+                }
+            }
+            
+            //
+            // Load neurons and weights
+            //
+            for(i=1; i<=alglib.ap.len(layersizes)-1; i++)
+            {
+                for(j=0; j<=layersizes[i]-1; j++)
+                {
+                    fkind = s.unserialize_int();
+                    threshold = s.unserialize_double();
+                    mlpsetneuroninfo(network, i, j, fkind, threshold);
+                    for(k=0; k<=layersizes[i-1]-1; k++)
+                    {
+                        v0 = s.unserialize_double();
+                        mlpsetweight(network, i-1, k, i, j, v0);
+                    }
+                }
+            }
+            
+            //
+            // Load standartizator
+            //
+            for(j=0; j<=nin-1; j++)
+            {
+                v0 = s.unserialize_double();
+                v1 = s.unserialize_double();
+                mlpsetinputscaling(network, j, v0, v1);
+            }
+            for(j=0; j<=nout-1; j++)
+            {
+                v0 = s.unserialize_double();
+                v1 = s.unserialize_double();
+                mlpsetoutputscaling(network, j, v0, v1);
+            }
+        }
+
+
+        /*************************************************************************
+        Internal subroutine: adding new input layer to network
+        *************************************************************************/
+        private static void addinputlayer(int ncount,
+            ref int[] lsizes,
+            ref int[] ltypes,
+            ref int[] lconnfirst,
+            ref int[] lconnlast,
+            ref int lastproc)
+        {
+            lsizes[0] = ncount;
+            ltypes[0] = -2;
+            lconnfirst[0] = 0;
+            lconnlast[0] = 0;
+            lastproc = 0;
+        }
+
+
+        /*************************************************************************
+        Internal subroutine: adding new summator layer to network
+        *************************************************************************/
+        private static void addbiasedsummatorlayer(int ncount,
+            ref int[] lsizes,
+            ref int[] ltypes,
+            ref int[] lconnfirst,
+            ref int[] lconnlast,
+            ref int lastproc)
+        {
+            lsizes[lastproc+1] = 1;
+            ltypes[lastproc+1] = -3;
+            lconnfirst[lastproc+1] = 0;
+            lconnlast[lastproc+1] = 0;
+            lsizes[lastproc+2] = ncount;
+            ltypes[lastproc+2] = 0;
+            lconnfirst[lastproc+2] = lastproc;
+            lconnlast[lastproc+2] = lastproc+1;
+            lastproc = lastproc+2;
+        }
+
+
+        /*************************************************************************
+        Internal subroutine: adding new summator layer to network
+        *************************************************************************/
+        private static void addactivationlayer(int functype,
+            ref int[] lsizes,
+            ref int[] ltypes,
+            ref int[] lconnfirst,
+            ref int[] lconnlast,
+            ref int lastproc)
+        {
+            alglib.ap.assert(functype>0 || functype==-5, "AddActivationLayer: incorrect function type");
+            lsizes[lastproc+1] = lsizes[lastproc];
+            ltypes[lastproc+1] = functype;
+            lconnfirst[lastproc+1] = lastproc;
+            lconnlast[lastproc+1] = lastproc;
+            lastproc = lastproc+1;
+        }
+
+
+        /*************************************************************************
+        Internal subroutine: adding new zero layer to network
+        *************************************************************************/
+        private static void addzerolayer(ref int[] lsizes,
+            ref int[] ltypes,
+            ref int[] lconnfirst,
+            ref int[] lconnlast,
+            ref int lastproc)
+        {
+            lsizes[lastproc+1] = 1;
+            ltypes[lastproc+1] = -4;
+            lconnfirst[lastproc+1] = 0;
+            lconnlast[lastproc+1] = 0;
+            lastproc = lastproc+1;
+        }
+
+
+        /*************************************************************************
+        This routine adds input layer to the high-level description of the network.
+
+        It modifies Network.HLConnections and Network.HLNeurons  and  assumes that
+        these  arrays  have  enough  place  to  store  data.  It accepts following
+        parameters:
+            Network     -   network
+            ConnIdx     -   index of the first free entry in the HLConnections
+            NeuroIdx    -   index of the first free entry in the HLNeurons
+            StructInfoIdx-  index of the first entry in the low level description
+                            of the current layer (in the StructInfo array)
+            NIn         -   number of inputs
+                            
+        It modified Network and indices.
+        *************************************************************************/
+        private static void hladdinputlayer(multilayerperceptron network,
+            ref int connidx,
+            ref int neuroidx,
+            ref int structinfoidx,
+            int nin)
+        {
+            int i = 0;
+            int offs = 0;
+
+            offs = hlnfieldwidth*neuroidx;
+            for(i=0; i<=nin-1; i++)
+            {
+                network.hlneurons[offs+0] = 0;
+                network.hlneurons[offs+1] = i;
+                network.hlneurons[offs+2] = -1;
+                network.hlneurons[offs+3] = -1;
+                offs = offs+hlnfieldwidth;
+            }
+            neuroidx = neuroidx+nin;
+            structinfoidx = structinfoidx+nin;
+        }
+
+
+        /*************************************************************************
+        This routine adds output layer to the high-level description of
+        the network.
+
+        It modifies Network.HLConnections and Network.HLNeurons  and  assumes that
+        these  arrays  have  enough  place  to  store  data.  It accepts following
+        parameters:
+            Network     -   network
+            ConnIdx     -   index of the first free entry in the HLConnections
+            NeuroIdx    -   index of the first free entry in the HLNeurons
+            StructInfoIdx-  index of the first entry in the low level description
+                            of the current layer (in the StructInfo array)
+            WeightsIdx  -   index of the first entry in the Weights array which
+                            corresponds to the current layer
+            K           -   current layer index
+            NPrev       -   number of neurons in the previous layer
+            NOut        -   number of outputs
+            IsCls       -   is it classifier network?
+            IsLinear    -   is it network with linear output?
+
+        It modified Network and ConnIdx/NeuroIdx/StructInfoIdx/WeightsIdx.
+        *************************************************************************/
+        private static void hladdoutputlayer(multilayerperceptron network,
+            ref int connidx,
+            ref int neuroidx,
+            ref int structinfoidx,
+            ref int weightsidx,
+            int k,
+            int nprev,
+            int nout,
+            bool iscls,
+            bool islinearout)
+        {
+            int i = 0;
+            int j = 0;
+            int neurooffs = 0;
+            int connoffs = 0;
+
+            alglib.ap.assert((iscls && islinearout) || !iscls, "HLAddOutputLayer: internal error");
+            neurooffs = hlnfieldwidth*neuroidx;
+            connoffs = hlconnfieldwidth*connidx;
+            if( !iscls )
+            {
+                
+                //
+                // Regression network
+                //
+                for(i=0; i<=nout-1; i++)
+                {
+                    network.hlneurons[neurooffs+0] = k;
+                    network.hlneurons[neurooffs+1] = i;
+                    network.hlneurons[neurooffs+2] = structinfoidx+1+nout+i;
+                    network.hlneurons[neurooffs+3] = weightsidx+nprev+(nprev+1)*i;
+                    neurooffs = neurooffs+hlnfieldwidth;
+                }
+                for(i=0; i<=nprev-1; i++)
+                {
+                    for(j=0; j<=nout-1; j++)
+                    {
+                        network.hlconnections[connoffs+0] = k-1;
+                        network.hlconnections[connoffs+1] = i;
+                        network.hlconnections[connoffs+2] = k;
+                        network.hlconnections[connoffs+3] = j;
+                        network.hlconnections[connoffs+4] = weightsidx+i+j*(nprev+1);
+                        connoffs = connoffs+hlconnfieldwidth;
+                    }
+                }
+                connidx = connidx+nprev*nout;
+                neuroidx = neuroidx+nout;
+                structinfoidx = structinfoidx+2*nout+1;
+                weightsidx = weightsidx+nout*(nprev+1);
+            }
+            else
+            {
+                
+                //
+                // Classification network
+                //
+                for(i=0; i<=nout-2; i++)
+                {
+                    network.hlneurons[neurooffs+0] = k;
+                    network.hlneurons[neurooffs+1] = i;
+                    network.hlneurons[neurooffs+2] = -1;
+                    network.hlneurons[neurooffs+3] = weightsidx+nprev+(nprev+1)*i;
+                    neurooffs = neurooffs+hlnfieldwidth;
+                }
+                network.hlneurons[neurooffs+0] = k;
+                network.hlneurons[neurooffs+1] = i;
+                network.hlneurons[neurooffs+2] = -1;
+                network.hlneurons[neurooffs+3] = -1;
+                for(i=0; i<=nprev-1; i++)
+                {
+                    for(j=0; j<=nout-2; j++)
+                    {
+                        network.hlconnections[connoffs+0] = k-1;
+                        network.hlconnections[connoffs+1] = i;
+                        network.hlconnections[connoffs+2] = k;
+                        network.hlconnections[connoffs+3] = j;
+                        network.hlconnections[connoffs+4] = weightsidx+i+j*(nprev+1);
+                        connoffs = connoffs+hlconnfieldwidth;
+                    }
+                }
+                connidx = connidx+nprev*(nout-1);
+                neuroidx = neuroidx+nout;
+                structinfoidx = structinfoidx+nout+2;
+                weightsidx = weightsidx+(nout-1)*(nprev+1);
+            }
+        }
+
+
+        /*************************************************************************
+        This routine adds hidden layer to the high-level description of
+        the network.
+
+        It modifies Network.HLConnections and Network.HLNeurons  and  assumes that
+        these  arrays  have  enough  place  to  store  data.  It accepts following
+        parameters:
+            Network     -   network
+            ConnIdx     -   index of the first free entry in the HLConnections
+            NeuroIdx    -   index of the first free entry in the HLNeurons
+            StructInfoIdx-  index of the first entry in the low level description
+                            of the current layer (in the StructInfo array)
+            WeightsIdx  -   index of the first entry in the Weights array which
+                            corresponds to the current layer
+            K           -   current layer index
+            NPrev       -   number of neurons in the previous layer
+            NCur        -   number of neurons in the current layer
+
+        It modified Network and ConnIdx/NeuroIdx/StructInfoIdx/WeightsIdx.
+        *************************************************************************/
+        private static void hladdhiddenlayer(multilayerperceptron network,
+            ref int connidx,
+            ref int neuroidx,
+            ref int structinfoidx,
+            ref int weightsidx,
+            int k,
+            int nprev,
+            int ncur)
+        {
+            int i = 0;
+            int j = 0;
+            int neurooffs = 0;
+            int connoffs = 0;
+
+            neurooffs = hlnfieldwidth*neuroidx;
+            connoffs = hlconnfieldwidth*connidx;
+            for(i=0; i<=ncur-1; i++)
+            {
+                network.hlneurons[neurooffs+0] = k;
+                network.hlneurons[neurooffs+1] = i;
+                network.hlneurons[neurooffs+2] = structinfoidx+1+ncur+i;
+                network.hlneurons[neurooffs+3] = weightsidx+nprev+(nprev+1)*i;
+                neurooffs = neurooffs+hlnfieldwidth;
+            }
+            for(i=0; i<=nprev-1; i++)
+            {
+                for(j=0; j<=ncur-1; j++)
+                {
+                    network.hlconnections[connoffs+0] = k-1;
+                    network.hlconnections[connoffs+1] = i;
+                    network.hlconnections[connoffs+2] = k;
+                    network.hlconnections[connoffs+3] = j;
+                    network.hlconnections[connoffs+4] = weightsidx+i+j*(nprev+1);
+                    connoffs = connoffs+hlconnfieldwidth;
+                }
+            }
+            connidx = connidx+nprev*ncur;
+            neuroidx = neuroidx+ncur;
+            structinfoidx = structinfoidx+2*ncur+1;
+            weightsidx = weightsidx+ncur*(nprev+1);
+        }
+
+
+        /*************************************************************************
+        This function fills high level information about network created using
+        internal MLPCreate() function.
+
+        This function does NOT examine StructInfo for low level information, it
+        just expects that network has following structure:
+
+            input neuron            \
+            ...                      | input layer
+            input neuron            /
+            
+            "-1" neuron             \
+            biased summator          |
+            ...                      |
+            biased summator          | hidden layer(s), if there are exists any
+            activation function      |
+            ...                      |
+            activation function     /
+            
+            "-1" neuron            \
+            biased summator         | output layer:
+            ...                     |
+            biased summator         | * we have NOut summators/activators for regression networks
+            activation function     | * we have only NOut-1 summators and no activators for classifiers
+            ...                     | * we have "0" neuron only when we have classifier
+            activation function     |
+            "0" neuron              /
+
+
+          -- ALGLIB --
+             Copyright 30.03.2008 by Bochkanov Sergey
+        *************************************************************************/
+        private static void fillhighlevelinformation(multilayerperceptron network,
+            int nin,
+            int nhid1,
+            int nhid2,
+            int nout,
+            bool iscls,
+            bool islinearout)
+        {
+            int idxweights = 0;
+            int idxstruct = 0;
+            int idxneuro = 0;
+            int idxconn = 0;
+
+            alglib.ap.assert((iscls && islinearout) || !iscls, "FillHighLevelInformation: internal error");
+            
+            //
+            // Preparations common to all types of networks
+            //
+            idxweights = 0;
+            idxneuro = 0;
+            idxstruct = 0;
+            idxconn = 0;
+            network.hlnetworktype = 0;
+            
+            //
+            // network without hidden layers
+            //
+            if( nhid1==0 )
+            {
+                network.hllayersizes = new int[2];
+                network.hllayersizes[0] = nin;
+                network.hllayersizes[1] = nout;
+                if( !iscls )
+                {
+                    network.hlconnections = new int[hlconnfieldwidth*nin*nout];
+                    network.hlneurons = new int[hlnfieldwidth*(nin+nout)];
+                    network.hlnormtype = 0;
+                }
+                else
+                {
+                    network.hlconnections = new int[hlconnfieldwidth*nin*(nout-1)];
+                    network.hlneurons = new int[hlnfieldwidth*(nin+nout)];
+                    network.hlnormtype = 1;
+                }
+                hladdinputlayer(network, ref idxconn, ref idxneuro, ref idxstruct, nin);
+                hladdoutputlayer(network, ref idxconn, ref idxneuro, ref idxstruct, ref idxweights, 1, nin, nout, iscls, islinearout);
+                return;
+            }
+            
+            //
+            // network with one hidden layers
+            //
+            if( nhid2==0 )
+            {
+                network.hllayersizes = new int[3];
+                network.hllayersizes[0] = nin;
+                network.hllayersizes[1] = nhid1;
+                network.hllayersizes[2] = nout;
+                if( !iscls )
+                {
+                    network.hlconnections = new int[hlconnfieldwidth*(nin*nhid1+nhid1*nout)];
+                    network.hlneurons = new int[hlnfieldwidth*(nin+nhid1+nout)];
+                    network.hlnormtype = 0;
+                }
+                else
+                {
+                    network.hlconnections = new int[hlconnfieldwidth*(nin*nhid1+nhid1*(nout-1))];
+                    network.hlneurons = new int[hlnfieldwidth*(nin+nhid1+nout)];
+                    network.hlnormtype = 1;
+                }
+                hladdinputlayer(network, ref idxconn, ref idxneuro, ref idxstruct, nin);
+                hladdhiddenlayer(network, ref idxconn, ref idxneuro, ref idxstruct, ref idxweights, 1, nin, nhid1);
+                hladdoutputlayer(network, ref idxconn, ref idxneuro, ref idxstruct, ref idxweights, 2, nhid1, nout, iscls, islinearout);
+                return;
+            }
+            
+            //
+            // Two hidden layers
+            //
+            network.hllayersizes = new int[4];
+            network.hllayersizes[0] = nin;
+            network.hllayersizes[1] = nhid1;
+            network.hllayersizes[2] = nhid2;
+            network.hllayersizes[3] = nout;
+            if( !iscls )
+            {
+                network.hlconnections = new int[hlconnfieldwidth*(nin*nhid1+nhid1*nhid2+nhid2*nout)];
+                network.hlneurons = new int[hlnfieldwidth*(nin+nhid1+nhid2+nout)];
+                network.hlnormtype = 0;
+            }
+            else
+            {
+                network.hlconnections = new int[hlconnfieldwidth*(nin*nhid1+nhid1*nhid2+nhid2*(nout-1))];
+                network.hlneurons = new int[hlnfieldwidth*(nin+nhid1+nhid2+nout)];
+                network.hlnormtype = 1;
+            }
+            hladdinputlayer(network, ref idxconn, ref idxneuro, ref idxstruct, nin);
+            hladdhiddenlayer(network, ref idxconn, ref idxneuro, ref idxstruct, ref idxweights, 1, nin, nhid1);
+            hladdhiddenlayer(network, ref idxconn, ref idxneuro, ref idxstruct, ref idxweights, 2, nhid1, nhid2);
+            hladdoutputlayer(network, ref idxconn, ref idxneuro, ref idxstruct, ref idxweights, 3, nhid2, nout, iscls, islinearout);
+        }
+
+
+        /*************************************************************************
+        Internal subroutine.
+
+          -- ALGLIB --
+             Copyright 04.11.2007 by Bochkanov Sergey
+        *************************************************************************/
+        private static void mlpcreate(int nin,
+            int nout,
+            int[] lsizes,
+            int[] ltypes,
+            int[] lconnfirst,
+            int[] lconnlast,
+            int layerscount,
+            bool isclsnet,
+            multilayerperceptron network)
+        {
+            int i = 0;
+            int j = 0;
+            int ssize = 0;
+            int ntotal = 0;
+            int wcount = 0;
+            int offs = 0;
+            int nprocessed = 0;
+            int wallocated = 0;
+            int[] localtemp = new int[0];
+            int[] lnfirst = new int[0];
+            int[] lnsyn = new int[0];
+
+            
+            //
+            // Check
+            //
+            alglib.ap.assert(layerscount>0, "MLPCreate: wrong parameters!");
+            alglib.ap.assert(ltypes[0]==-2, "MLPCreate: wrong LTypes[0] (must be -2)!");
+            for(i=0; i<=layerscount-1; i++)
+            {
+                alglib.ap.assert(lsizes[i]>0, "MLPCreate: wrong LSizes!");
+                alglib.ap.assert(lconnfirst[i]>=0 && (lconnfirst[i]<i || i==0), "MLPCreate: wrong LConnFirst!");
+                alglib.ap.assert(lconnlast[i]>=lconnfirst[i] && (lconnlast[i]<i || i==0), "MLPCreate: wrong LConnLast!");
+            }
+            
+            //
+            // Build network geometry
+            //
+            lnfirst = new int[layerscount-1+1];
+            lnsyn = new int[layerscount-1+1];
+            ntotal = 0;
+            wcount = 0;
+            for(i=0; i<=layerscount-1; i++)
+            {
+                
+                //
+                // Analyze connections.
+                // This code must throw an assertion in case of unknown LTypes[I]
+                //
+                lnsyn[i] = -1;
+                if( ltypes[i]>=0 || ltypes[i]==-5 )
+                {
+                    lnsyn[i] = 0;
+                    for(j=lconnfirst[i]; j<=lconnlast[i]; j++)
+                    {
+                        lnsyn[i] = lnsyn[i]+lsizes[j];
+                    }
+                }
+                else
+                {
+                    if( (ltypes[i]==-2 || ltypes[i]==-3) || ltypes[i]==-4 )
+                    {
+                        lnsyn[i] = 0;
+                    }
+                }
+                alglib.ap.assert(lnsyn[i]>=0, "MLPCreate: internal error #0!");
+                
+                //
+                // Other info
+                //
+                lnfirst[i] = ntotal;
+                ntotal = ntotal+lsizes[i];
+                if( ltypes[i]==0 )
+                {
+                    wcount = wcount+lnsyn[i]*lsizes[i];
+                }
+            }
+            ssize = 7+ntotal*nfieldwidth;
+            
+            //
+            // Allocate
+            //
+            network.structinfo = new int[ssize-1+1];
+            network.weights = new double[wcount-1+1];
+            if( isclsnet )
+            {
+                network.columnmeans = new double[nin-1+1];
+                network.columnsigmas = new double[nin-1+1];
+            }
+            else
+            {
+                network.columnmeans = new double[nin+nout-1+1];
+                network.columnsigmas = new double[nin+nout-1+1];
+            }
+            network.neurons = new double[ntotal-1+1];
+            network.chunks = new double[3*ntotal+1, chunksize-1+1];
+            network.nwbuf = new double[Math.Max(wcount, 2*nout)-1+1];
+            network.integerbuf = new int[3+1];
+            network.dfdnet = new double[ntotal-1+1];
+            network.x = new double[nin-1+1];
+            network.y = new double[nout-1+1];
+            network.derror = new double[ntotal-1+1];
+            
+            //
+            // Fill structure: global info
+            //
+            network.structinfo[0] = ssize;
+            network.structinfo[1] = nin;
+            network.structinfo[2] = nout;
+            network.structinfo[3] = ntotal;
+            network.structinfo[4] = wcount;
+            network.structinfo[5] = 7;
+            if( isclsnet )
+            {
+                network.structinfo[6] = 1;
+            }
+            else
+            {
+                network.structinfo[6] = 0;
+            }
+            
+            //
+            // Fill structure: neuron connections
+            //
+            nprocessed = 0;
+            wallocated = 0;
+            for(i=0; i<=layerscount-1; i++)
+            {
+                for(j=0; j<=lsizes[i]-1; j++)
+                {
+                    offs = network.structinfo[5]+nprocessed*nfieldwidth;
+                    network.structinfo[offs+0] = ltypes[i];
+                    if( ltypes[i]==0 )
+                    {
+                        
+                        //
+                        // Adaptive summator:
+                        // * connections with weights to previous neurons
+                        //
+                        network.structinfo[offs+1] = lnsyn[i];
+                        network.structinfo[offs+2] = lnfirst[lconnfirst[i]];
+                        network.structinfo[offs+3] = wallocated;
+                        wallocated = wallocated+lnsyn[i];
+                        nprocessed = nprocessed+1;
+                    }
+                    if( ltypes[i]>0 || ltypes[i]==-5 )
+                    {
+                        
+                        //
+                        // Activation layer:
+                        // * each neuron connected to one (only one) of previous neurons.
+                        // * no weights
+                        //
+                        network.structinfo[offs+1] = 1;
+                        network.structinfo[offs+2] = lnfirst[lconnfirst[i]]+j;
+                        network.structinfo[offs+3] = -1;
+                        nprocessed = nprocessed+1;
+                    }
+                    if( (ltypes[i]==-2 || ltypes[i]==-3) || ltypes[i]==-4 )
+                    {
+                        nprocessed = nprocessed+1;
+                    }
+                }
+            }
+            alglib.ap.assert(wallocated==wcount, "MLPCreate: internal error #1!");
+            alglib.ap.assert(nprocessed==ntotal, "MLPCreate: internal error #2!");
+            
+            //
+            // Fill weights by small random values
+            // Initialize means and sigmas
+            //
+            for(i=0; i<=wcount-1; i++)
+            {
+                network.weights[i] = math.randomreal()-0.5;
+            }
+            for(i=0; i<=nin-1; i++)
+            {
+                network.columnmeans[i] = 0;
+                network.columnsigmas[i] = 1;
+            }
+            if( !isclsnet )
+            {
+                for(i=0; i<=nout-1; i++)
+                {
+                    network.columnmeans[nin+i] = 0;
+                    network.columnsigmas[nin+i] = 1;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Internal subroutine for Hessian calculation.
+
+        WARNING!!! Unspeakable math far beyong human capabilities :)
+        *************************************************************************/
+        private static void mlphessianbatchinternal(multilayerperceptron network,
+            double[,] xy,
+            int ssize,
+            bool naturalerr,
+            ref double e,
+            ref double[] grad,
+            ref double[,] h)
+        {
+            int nin = 0;
+            int nout = 0;
+            int wcount = 0;
+            int ntotal = 0;
+            int istart = 0;
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int kl = 0;
+            int offs = 0;
+            int n1 = 0;
+            int n2 = 0;
+            int w1 = 0;
+            int w2 = 0;
+            double s = 0;
+            double t = 0;
+            double v = 0;
+            double et = 0;
+            bool bflag = new bool();
+            double f = 0;
+            double df = 0;
+            double d2f = 0;
+            double deidyj = 0;
+            double mx = 0;
+            double q = 0;
+            double z = 0;
+            double s2 = 0;
+            double expi = 0;
+            double expj = 0;
+            double[] x = new double[0];
+            double[] desiredy = new double[0];
+            double[] gt = new double[0];
+            double[] zeros = new double[0];
+            double[,] rx = new double[0,0];
+            double[,] ry = new double[0,0];
+            double[,] rdx = new double[0,0];
+            double[,] rdy = new double[0,0];
+            int i_ = 0;
+            int i1_ = 0;
+
+            e = 0;
+
+            mlpproperties(network, ref nin, ref nout, ref wcount);
+            ntotal = network.structinfo[3];
+            istart = network.structinfo[5];
+            
+            //
+            // Prepare
+            //
+            x = new double[nin-1+1];
+            desiredy = new double[nout-1+1];
+            zeros = new double[wcount-1+1];
+            gt = new double[wcount-1+1];
+            rx = new double[ntotal+nout-1+1, wcount-1+1];
+            ry = new double[ntotal+nout-1+1, wcount-1+1];
+            rdx = new double[ntotal+nout-1+1, wcount-1+1];
+            rdy = new double[ntotal+nout-1+1, wcount-1+1];
+            e = 0;
+            for(i=0; i<=wcount-1; i++)
+            {
+                zeros[i] = 0;
+            }
+            for(i_=0; i_<=wcount-1;i_++)
+            {
+                grad[i_] = zeros[i_];
+            }
+            for(i=0; i<=wcount-1; i++)
+            {
+                for(i_=0; i_<=wcount-1;i_++)
+                {
+                    h[i,i_] = zeros[i_];
+                }
+            }
+            
+            //
+            // Process
+            //
+            for(k=0; k<=ssize-1; k++)
+            {
+                
+                //
+                // Process vector with MLPGradN.
+                // Now Neurons, DFDNET and DError contains results of the last run.
+                //
+                for(i_=0; i_<=nin-1;i_++)
+                {
+                    x[i_] = xy[k,i_];
+                }
+                if( mlpissoftmax(network) )
+                {
+                    
+                    //
+                    // class labels outputs
+                    //
+                    kl = (int)Math.Round(xy[k,nin]);
+                    for(i=0; i<=nout-1; i++)
+                    {
+                        if( i==kl )
+                        {
+                            desiredy[i] = 1;
+                        }
+                        else
+                        {
+                            desiredy[i] = 0;
+                        }
+                    }
+                }
+                else
+                {
+                    
+                    //
+                    // real outputs
+                    //
+                    i1_ = (nin) - (0);
+                    for(i_=0; i_<=nout-1;i_++)
+                    {
+                        desiredy[i_] = xy[k,i_+i1_];
+                    }
+                }
+                if( naturalerr )
+                {
+                    mlpgradn(network, x, desiredy, ref et, ref gt);
+                }
+                else
+                {
+                    mlpgrad(network, x, desiredy, ref et, ref gt);
+                }
+                
+                //
+                // grad, error
+                //
+                e = e+et;
+                for(i_=0; i_<=wcount-1;i_++)
+                {
+                    grad[i_] = grad[i_] + gt[i_];
+                }
+                
+                //
+                // Hessian.
+                // Forward pass of the R-algorithm
+                //
+                for(i=0; i<=ntotal-1; i++)
+                {
+                    offs = istart+i*nfieldwidth;
+                    for(i_=0; i_<=wcount-1;i_++)
+                    {
+                        rx[i,i_] = zeros[i_];
+                    }
+                    for(i_=0; i_<=wcount-1;i_++)
+                    {
+                        ry[i,i_] = zeros[i_];
+                    }
+                    if( network.structinfo[offs+0]>0 || network.structinfo[offs+0]==-5 )
+                    {
+                        
+                        //
+                        // Activation function
+                        //
+                        n1 = network.structinfo[offs+2];
+                        for(i_=0; i_<=wcount-1;i_++)
+                        {
+                            rx[i,i_] = ry[n1,i_];
+                        }
+                        v = network.dfdnet[i];
+                        for(i_=0; i_<=wcount-1;i_++)
+                        {
+                            ry[i,i_] = v*rx[i,i_];
+                        }
+                        continue;
+                    }
+                    if( network.structinfo[offs+0]==0 )
+                    {
+                        
+                        //
+                        // Adaptive summator
+                        //
+                        n1 = network.structinfo[offs+2];
+                        n2 = n1+network.structinfo[offs+1]-1;
+                        w1 = network.structinfo[offs+3];
+                        w2 = w1+network.structinfo[offs+1]-1;
+                        for(j=n1; j<=n2; j++)
+                        {
+                            v = network.weights[w1+j-n1];
+                            for(i_=0; i_<=wcount-1;i_++)
+                            {
+                                rx[i,i_] = rx[i,i_] + v*ry[j,i_];
+                            }
+                            rx[i,w1+j-n1] = rx[i,w1+j-n1]+network.neurons[j];
+                        }
+                        for(i_=0; i_<=wcount-1;i_++)
+                        {
+                            ry[i,i_] = rx[i,i_];
+                        }
+                        continue;
+                    }
+                    if( network.structinfo[offs+0]<0 )
+                    {
+                        bflag = true;
+                        if( network.structinfo[offs+0]==-2 )
+                        {
+                            
+                            //
+                            // input neuron, left unchanged
+                            //
+                            bflag = false;
+                        }
+                        if( network.structinfo[offs+0]==-3 )
+                        {
+                            
+                            //
+                            // "-1" neuron, left unchanged
+                            //
+                            bflag = false;
+                        }
+                        if( network.structinfo[offs+0]==-4 )
+                        {
+                            
+                            //
+                            // "0" neuron, left unchanged
+                            //
+                            bflag = false;
+                        }
+                        alglib.ap.assert(!bflag, "MLPHessianNBatch: internal error - unknown neuron type!");
+                        continue;
+                    }
+                }
+                
+                //
+                // Hessian. Backward pass of the R-algorithm.
+                //
+                // Stage 1. Initialize RDY
+                //
+                for(i=0; i<=ntotal+nout-1; i++)
+                {
+                    for(i_=0; i_<=wcount-1;i_++)
+                    {
+                        rdy[i,i_] = zeros[i_];
+                    }
+                }
+                if( network.structinfo[6]==0 )
+                {
+                    
+                    //
+                    // Standardisation.
+                    //
+                    // In context of the Hessian calculation standardisation
+                    // is considered as additional layer with weightless
+                    // activation function:
+                    //
+                    // F(NET) := Sigma*NET
+                    //
+                    // So we add one more layer to forward pass, and
+                    // make forward/backward pass through this layer.
+                    //
+                    for(i=0; i<=nout-1; i++)
+                    {
+                        n1 = ntotal-nout+i;
+                        n2 = ntotal+i;
+                        
+                        //
+                        // Forward pass from N1 to N2
+                        //
+                        for(i_=0; i_<=wcount-1;i_++)
+                        {
+                            rx[n2,i_] = ry[n1,i_];
+                        }
+                        v = network.columnsigmas[nin+i];
+                        for(i_=0; i_<=wcount-1;i_++)
+                        {
+                            ry[n2,i_] = v*rx[n2,i_];
+                        }
+                        
+                        //
+                        // Initialization of RDY
+                        //
+                        for(i_=0; i_<=wcount-1;i_++)
+                        {
+                            rdy[n2,i_] = ry[n2,i_];
+                        }
+                        
+                        //
+                        // Backward pass from N2 to N1:
+                        // 1. Calculate R(dE/dX).
+                        // 2. No R(dE/dWij) is needed since weight of activation neuron
+                        //    is fixed to 1. So we can update R(dE/dY) for
+                        //    the connected neuron (note that Vij=0, Wij=1)
+                        //
+                        df = network.columnsigmas[nin+i];
+                        for(i_=0; i_<=wcount-1;i_++)
+                        {
+                            rdx[n2,i_] = df*rdy[n2,i_];
+                        }
+                        for(i_=0; i_<=wcount-1;i_++)
+                        {
+                            rdy[n1,i_] = rdy[n1,i_] + rdx[n2,i_];
+                        }
+                    }
+                }
+                else
+                {
+                    
+                    //
+                    // Softmax.
+                    //
+                    // Initialize RDY using generalized expression for ei'(yi)
+                    // (see expression (9) from p. 5 of "Fast Exact Multiplication by the Hessian").
+                    //
+                    // When we are working with softmax network, generalized
+                    // expression for ei'(yi) is used because softmax
+                    // normalization leads to ei, which depends on all y's
+                    //
+                    if( naturalerr )
+                    {
+                        
+                        //
+                        // softmax + cross-entropy.
+                        // We have:
+                        //
+                        // S = sum(exp(yk)),
+                        // ei = sum(trn)*exp(yi)/S-trn_i
+                        //
+                        // j=i:   d(ei)/d(yj) = T*exp(yi)*(S-exp(yi))/S^2
+                        // j<>i:  d(ei)/d(yj) = -T*exp(yi)*exp(yj)/S^2
+                        //
+                        t = 0;
+                        for(i=0; i<=nout-1; i++)
+                        {
+                            t = t+desiredy[i];
+                        }
+                        mx = network.neurons[ntotal-nout];
+                        for(i=0; i<=nout-1; i++)
+                        {
+                            mx = Math.Max(mx, network.neurons[ntotal-nout+i]);
+                        }
+                        s = 0;
+                        for(i=0; i<=nout-1; i++)
+                        {
+                            network.nwbuf[i] = Math.Exp(network.neurons[ntotal-nout+i]-mx);
+                            s = s+network.nwbuf[i];
+                        }
+                        for(i=0; i<=nout-1; i++)
+                        {
+                            for(j=0; j<=nout-1; j++)
+                            {
+                                if( j==i )
+                                {
+                                    deidyj = t*network.nwbuf[i]*(s-network.nwbuf[i])/math.sqr(s);
+                                    for(i_=0; i_<=wcount-1;i_++)
+                                    {
+                                        rdy[ntotal-nout+i,i_] = rdy[ntotal-nout+i,i_] + deidyj*ry[ntotal-nout+i,i_];
+                                    }
+                                }
+                                else
+                                {
+                                    deidyj = -(t*network.nwbuf[i]*network.nwbuf[j]/math.sqr(s));
+                                    for(i_=0; i_<=wcount-1;i_++)
+                                    {
+                                        rdy[ntotal-nout+i,i_] = rdy[ntotal-nout+i,i_] + deidyj*ry[ntotal-nout+j,i_];
+                                    }
+                                }
+                            }
+                        }
+                    }
+                    else
+                    {
+                        
+                        //
+                        // For a softmax + squared error we have expression
+                        // far beyond human imagination so we dont even try
+                        // to comment on it. Just enjoy the code...
+                        //
+                        // P.S. That's why "natural error" is called "natural" -
+                        // compact beatiful expressions, fast code....
+                        //
+                        mx = network.neurons[ntotal-nout];
+                        for(i=0; i<=nout-1; i++)
+                        {
+                            mx = Math.Max(mx, network.neurons[ntotal-nout+i]);
+                        }
+                        s = 0;
+                        s2 = 0;
+                        for(i=0; i<=nout-1; i++)
+                        {
+                            network.nwbuf[i] = Math.Exp(network.neurons[ntotal-nout+i]-mx);
+                            s = s+network.nwbuf[i];
+                            s2 = s2+math.sqr(network.nwbuf[i]);
+                        }
+                        q = 0;
+                        for(i=0; i<=nout-1; i++)
+                        {
+                            q = q+(network.y[i]-desiredy[i])*network.nwbuf[i];
+                        }
+                        for(i=0; i<=nout-1; i++)
+                        {
+                            z = -q+(network.y[i]-desiredy[i])*s;
+                            expi = network.nwbuf[i];
+                            for(j=0; j<=nout-1; j++)
+                            {
+                                expj = network.nwbuf[j];
+                                if( j==i )
+                                {
+                                    deidyj = expi/math.sqr(s)*((z+expi)*(s-2*expi)/s+expi*s2/math.sqr(s));
+                                }
+                                else
+                                {
+                                    deidyj = expi*expj/math.sqr(s)*(s2/math.sqr(s)-2*z/s-(expi+expj)/s+(network.y[i]-desiredy[i])-(network.y[j]-desiredy[j]));
+                                }
+                                for(i_=0; i_<=wcount-1;i_++)
+                                {
+                                    rdy[ntotal-nout+i,i_] = rdy[ntotal-nout+i,i_] + deidyj*ry[ntotal-nout+j,i_];
+                                }
+                            }
+                        }
+                    }
+                }
+                
+                //
+                // Hessian. Backward pass of the R-algorithm
+                //
+                // Stage 2. Process.
+                //
+                for(i=ntotal-1; i>=0; i--)
+                {
+                    
+                    //
+                    // Possible variants:
+                    // 1. Activation function
+                    // 2. Adaptive summator
+                    // 3. Special neuron
+                    //
+                    offs = istart+i*nfieldwidth;
+                    if( network.structinfo[offs+0]>0 || network.structinfo[offs+0]==-5 )
+                    {
+                        n1 = network.structinfo[offs+2];
+                        
+                        //
+                        // First, calculate R(dE/dX).
+                        //
+                        mlpactivationfunction(network.neurons[n1], network.structinfo[offs+0], ref f, ref df, ref d2f);
+                        v = d2f*network.derror[i];
+                        for(i_=0; i_<=wcount-1;i_++)
+                        {
+                            rdx[i,i_] = df*rdy[i,i_];
+                        }
+                        for(i_=0; i_<=wcount-1;i_++)
+                        {
+                            rdx[i,i_] = rdx[i,i_] + v*rx[i,i_];
+                        }
+                        
+                        //
+                        // No R(dE/dWij) is needed since weight of activation neuron
+                        // is fixed to 1.
+                        //
+                        // So we can update R(dE/dY) for the connected neuron.
+                        // (note that Vij=0, Wij=1)
+                        //
+                        for(i_=0; i_<=wcount-1;i_++)
+                        {
+                            rdy[n1,i_] = rdy[n1,i_] + rdx[i,i_];
+                        }
+                        continue;
+                    }
+                    if( network.structinfo[offs+0]==0 )
+                    {
+                        
+                        //
+                        // Adaptive summator
+                        //
+                        n1 = network.structinfo[offs+2];
+                        n2 = n1+network.structinfo[offs+1]-1;
+                        w1 = network.structinfo[offs+3];
+                        w2 = w1+network.structinfo[offs+1]-1;
+                        
+                        //
+                        // First, calculate R(dE/dX).
+                        //
+                        for(i_=0; i_<=wcount-1;i_++)
+                        {
+                            rdx[i,i_] = rdy[i,i_];
+                        }
+                        
+                        //
+                        // Then, calculate R(dE/dWij)
+                        //
+                        for(j=w1; j<=w2; j++)
+                        {
+                            v = network.neurons[n1+j-w1];
+                            for(i_=0; i_<=wcount-1;i_++)
+                            {
+                                h[j,i_] = h[j,i_] + v*rdx[i,i_];
+                            }
+                            v = network.derror[i];
+                            for(i_=0; i_<=wcount-1;i_++)
+                            {
+                                h[j,i_] = h[j,i_] + v*ry[n1+j-w1,i_];
+                            }
+                        }
+                        
+                        //
+                        // And finally, update R(dE/dY) for connected neurons.
+                        //
+                        for(j=w1; j<=w2; j++)
+                        {
+                            v = network.weights[j];
+                            for(i_=0; i_<=wcount-1;i_++)
+                            {
+                                rdy[n1+j-w1,i_] = rdy[n1+j-w1,i_] + v*rdx[i,i_];
+                            }
+                            rdy[n1+j-w1,j] = rdy[n1+j-w1,j]+network.derror[i];
+                        }
+                        continue;
+                    }
+                    if( network.structinfo[offs+0]<0 )
+                    {
+                        bflag = false;
+                        if( (network.structinfo[offs+0]==-2 || network.structinfo[offs+0]==-3) || network.structinfo[offs+0]==-4 )
+                        {
+                            
+                            //
+                            // Special neuron type, no back-propagation required
+                            //
+                            bflag = true;
+                        }
+                        alglib.ap.assert(bflag, "MLPHessianNBatch: unknown neuron type!");
+                        continue;
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Internal subroutine
+
+        Network must be processed by MLPProcess on X
+        *************************************************************************/
+        private static void mlpinternalcalculategradient(multilayerperceptron network,
+            double[] neurons,
+            double[] weights,
+            ref double[] derror,
+            ref double[] grad,
+            bool naturalerrorfunc)
+        {
+            int i = 0;
+            int n1 = 0;
+            int n2 = 0;
+            int w1 = 0;
+            int w2 = 0;
+            int ntotal = 0;
+            int istart = 0;
+            int nin = 0;
+            int nout = 0;
+            int offs = 0;
+            double dedf = 0;
+            double dfdnet = 0;
+            double v = 0;
+            double fown = 0;
+            double deown = 0;
+            double net = 0;
+            double mx = 0;
+            bool bflag = new bool();
+            int i_ = 0;
+            int i1_ = 0;
+
+            
+            //
+            // Read network geometry
+            //
+            nin = network.structinfo[1];
+            nout = network.structinfo[2];
+            ntotal = network.structinfo[3];
+            istart = network.structinfo[5];
+            
+            //
+            // Pre-processing of dError/dOut:
+            // from dError/dOut(normalized) to dError/dOut(non-normalized)
+            //
+            alglib.ap.assert(network.structinfo[6]==0 || network.structinfo[6]==1, "MLPInternalCalculateGradient: unknown normalization type!");
+            if( network.structinfo[6]==1 )
+            {
+                
+                //
+                // Softmax
+                //
+                if( !naturalerrorfunc )
+                {
+                    mx = network.neurons[ntotal-nout];
+                    for(i=0; i<=nout-1; i++)
+                    {
+                        mx = Math.Max(mx, network.neurons[ntotal-nout+i]);
+                    }
+                    net = 0;
+                    for(i=0; i<=nout-1; i++)
+                    {
+                        network.nwbuf[i] = Math.Exp(network.neurons[ntotal-nout+i]-mx);
+                        net = net+network.nwbuf[i];
+                    }
+                    i1_ = (0)-(ntotal-nout);
+                    v = 0.0;
+                    for(i_=ntotal-nout; i_<=ntotal-1;i_++)
+                    {
+                        v += network.derror[i_]*network.nwbuf[i_+i1_];
+                    }
+                    for(i=0; i<=nout-1; i++)
+                    {
+                        fown = network.nwbuf[i];
+                        deown = network.derror[ntotal-nout+i];
+                        network.nwbuf[nout+i] = (-v+deown*fown+deown*(net-fown))*fown/math.sqr(net);
+                    }
+                    for(i=0; i<=nout-1; i++)
+                    {
+                        network.derror[ntotal-nout+i] = network.nwbuf[nout+i];
+                    }
+                }
+            }
+            else
+            {
+                
+                //
+                // Un-standardisation
+                //
+                for(i=0; i<=nout-1; i++)
+                {
+                    network.derror[ntotal-nout+i] = network.derror[ntotal-nout+i]*network.columnsigmas[nin+i];
+                }
+            }
+            
+            //
+            // Backpropagation
+            //
+            for(i=ntotal-1; i>=0; i--)
+            {
+                
+                //
+                // Extract info
+                //
+                offs = istart+i*nfieldwidth;
+                if( network.structinfo[offs+0]>0 || network.structinfo[offs+0]==-5 )
+                {
+                    
+                    //
+                    // Activation function
+                    //
+                    dedf = network.derror[i];
+                    dfdnet = network.dfdnet[i];
+                    derror[network.structinfo[offs+2]] = derror[network.structinfo[offs+2]]+dedf*dfdnet;
+                    continue;
+                }
+                if( network.structinfo[offs+0]==0 )
+                {
+                    
+                    //
+                    // Adaptive summator
+                    //
+                    n1 = network.structinfo[offs+2];
+                    n2 = n1+network.structinfo[offs+1]-1;
+                    w1 = network.structinfo[offs+3];
+                    w2 = w1+network.structinfo[offs+1]-1;
+                    dedf = network.derror[i];
+                    dfdnet = 1.0;
+                    v = dedf*dfdnet;
+                    i1_ = (n1) - (w1);
+                    for(i_=w1; i_<=w2;i_++)
+                    {
+                        grad[i_] = v*neurons[i_+i1_];
+                    }
+                    i1_ = (w1) - (n1);
+                    for(i_=n1; i_<=n2;i_++)
+                    {
+                        derror[i_] = derror[i_] + v*weights[i_+i1_];
+                    }
+                    continue;
+                }
+                if( network.structinfo[offs+0]<0 )
+                {
+                    bflag = false;
+                    if( (network.structinfo[offs+0]==-2 || network.structinfo[offs+0]==-3) || network.structinfo[offs+0]==-4 )
+                    {
+                        
+                        //
+                        // Special neuron type, no back-propagation required
+                        //
+                        bflag = true;
+                    }
+                    alglib.ap.assert(bflag, "MLPInternalCalculateGradient: unknown neuron type!");
+                    continue;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Internal subroutine, chunked gradient
+        *************************************************************************/
+        private static void mlpchunkedgradient(multilayerperceptron network,
+            double[,] xy,
+            int cstart,
+            int csize,
+            ref double e,
+            ref double[] grad,
+            bool naturalerrorfunc)
+        {
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int kl = 0;
+            int n1 = 0;
+            int n2 = 0;
+            int w1 = 0;
+            int w2 = 0;
+            int c1 = 0;
+            int c2 = 0;
+            int ntotal = 0;
+            int nin = 0;
+            int nout = 0;
+            int offs = 0;
+            double f = 0;
+            double df = 0;
+            double d2f = 0;
+            double v = 0;
+            double s = 0;
+            double fown = 0;
+            double deown = 0;
+            double net = 0;
+            double lnnet = 0;
+            double mx = 0;
+            bool bflag = new bool();
+            int istart = 0;
+            int ineurons = 0;
+            int idfdnet = 0;
+            int iderror = 0;
+            int izeros = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            
+            //
+            // Read network geometry, prepare data
+            //
+            nin = network.structinfo[1];
+            nout = network.structinfo[2];
+            ntotal = network.structinfo[3];
+            istart = network.structinfo[5];
+            c1 = cstart;
+            c2 = cstart+csize-1;
+            ineurons = 0;
+            idfdnet = ntotal;
+            iderror = 2*ntotal;
+            izeros = 3*ntotal;
+            for(j=0; j<=csize-1; j++)
+            {
+                network.chunks[izeros,j] = 0;
+            }
+            
+            //
+            // Forward pass:
+            // 1. Load inputs from XY to Chunks[0:NIn-1,0:CSize-1]
+            // 2. Forward pass
+            //
+            for(i=0; i<=nin-1; i++)
+            {
+                for(j=0; j<=csize-1; j++)
+                {
+                    if( (double)(network.columnsigmas[i])!=(double)(0) )
+                    {
+                        network.chunks[i,j] = (xy[c1+j,i]-network.columnmeans[i])/network.columnsigmas[i];
+                    }
+                    else
+                    {
+                        network.chunks[i,j] = xy[c1+j,i]-network.columnmeans[i];
+                    }
+                }
+            }
+            for(i=0; i<=ntotal-1; i++)
+            {
+                offs = istart+i*nfieldwidth;
+                if( network.structinfo[offs+0]>0 || network.structinfo[offs+0]==-5 )
+                {
+                    
+                    //
+                    // Activation function:
+                    // * calculate F vector, F(i) = F(NET(i))
+                    //
+                    n1 = network.structinfo[offs+2];
+                    for(i_=0; i_<=csize-1;i_++)
+                    {
+                        network.chunks[i,i_] = network.chunks[n1,i_];
+                    }
+                    for(j=0; j<=csize-1; j++)
+                    {
+                        mlpactivationfunction(network.chunks[i,j], network.structinfo[offs+0], ref f, ref df, ref d2f);
+                        network.chunks[i,j] = f;
+                        network.chunks[idfdnet+i,j] = df;
+                    }
+                    continue;
+                }
+                if( network.structinfo[offs+0]==0 )
+                {
+                    
+                    //
+                    // Adaptive summator:
+                    // * calculate NET vector, NET(i) = SUM(W(j,i)*Neurons(j),j=N1..N2)
+                    //
+                    n1 = network.structinfo[offs+2];
+                    n2 = n1+network.structinfo[offs+1]-1;
+                    w1 = network.structinfo[offs+3];
+                    w2 = w1+network.structinfo[offs+1]-1;
+                    for(i_=0; i_<=csize-1;i_++)
+                    {
+                        network.chunks[i,i_] = network.chunks[izeros,i_];
+                    }
+                    for(j=n1; j<=n2; j++)
+                    {
+                        v = network.weights[w1+j-n1];
+                        for(i_=0; i_<=csize-1;i_++)
+                        {
+                            network.chunks[i,i_] = network.chunks[i,i_] + v*network.chunks[j,i_];
+                        }
+                    }
+                    continue;
+                }
+                if( network.structinfo[offs+0]<0 )
+                {
+                    bflag = false;
+                    if( network.structinfo[offs+0]==-2 )
+                    {
+                        
+                        //
+                        // input neuron, left unchanged
+                        //
+                        bflag = true;
+                    }
+                    if( network.structinfo[offs+0]==-3 )
+                    {
+                        
+                        //
+                        // "-1" neuron
+                        //
+                        for(k=0; k<=csize-1; k++)
+                        {
+                            network.chunks[i,k] = -1;
+                        }
+                        bflag = true;
+                    }
+                    if( network.structinfo[offs+0]==-4 )
+                    {
+                        
+                        //
+                        // "0" neuron
+                        //
+                        for(k=0; k<=csize-1; k++)
+                        {
+                            network.chunks[i,k] = 0;
+                        }
+                        bflag = true;
+                    }
+                    alglib.ap.assert(bflag, "MLPChunkedGradient: internal error - unknown neuron type!");
+                    continue;
+                }
+            }
+            
+            //
+            // Post-processing, error, dError/dOut
+            //
+            for(i=0; i<=ntotal-1; i++)
+            {
+                for(i_=0; i_<=csize-1;i_++)
+                {
+                    network.chunks[iderror+i,i_] = network.chunks[izeros,i_];
+                }
+            }
+            alglib.ap.assert(network.structinfo[6]==0 || network.structinfo[6]==1, "MLPChunkedGradient: unknown normalization type!");
+            if( network.structinfo[6]==1 )
+            {
+                
+                //
+                // Softmax output, classification network.
+                //
+                // For each K = 0..CSize-1 do:
+                // 1. place exp(outputs[k]) to NWBuf[0:NOut-1]
+                // 2. place sum(exp(..)) to NET
+                // 3. calculate dError/dOut and place it to the second block of Chunks
+                //
+                for(k=0; k<=csize-1; k++)
+                {
+                    
+                    //
+                    // Normalize
+                    //
+                    mx = network.chunks[ntotal-nout,k];
+                    for(i=1; i<=nout-1; i++)
+                    {
+                        mx = Math.Max(mx, network.chunks[ntotal-nout+i,k]);
+                    }
+                    net = 0;
+                    for(i=0; i<=nout-1; i++)
+                    {
+                        network.nwbuf[i] = Math.Exp(network.chunks[ntotal-nout+i,k]-mx);
+                        net = net+network.nwbuf[i];
+                    }
+                    
+                    //
+                    // Calculate error function and dError/dOut
+                    //
+                    if( naturalerrorfunc )
+                    {
+                        
+                        //
+                        // Natural error func.
+                        //
+                        //
+                        s = 1;
+                        lnnet = Math.Log(net);
+                        kl = (int)Math.Round(xy[cstart+k,nin]);
+                        for(i=0; i<=nout-1; i++)
+                        {
+                            if( i==kl )
+                            {
+                                v = 1;
+                            }
+                            else
+                            {
+                                v = 0;
+                            }
+                            network.chunks[iderror+ntotal-nout+i,k] = s*network.nwbuf[i]/net-v;
+                            e = e+safecrossentropy(v, network.nwbuf[i]/net);
+                        }
+                    }
+                    else
+                    {
+                        
+                        //
+                        // Least squares error func
+                        // Error, dError/dOut(normalized)
+                        //
+                        kl = (int)Math.Round(xy[cstart+k,nin]);
+                        for(i=0; i<=nout-1; i++)
+                        {
+                            if( i==kl )
+                            {
+                                v = network.nwbuf[i]/net-1;
+                            }
+                            else
+                            {
+                                v = network.nwbuf[i]/net;
+                            }
+                            network.nwbuf[nout+i] = v;
+                            e = e+math.sqr(v)/2;
+                        }
+                        
+                        //
+                        // From dError/dOut(normalized) to dError/dOut(non-normalized)
+                        //
+                        i1_ = (0)-(nout);
+                        v = 0.0;
+                        for(i_=nout; i_<=2*nout-1;i_++)
+                        {
+                            v += network.nwbuf[i_]*network.nwbuf[i_+i1_];
+                        }
+                        for(i=0; i<=nout-1; i++)
+                        {
+                            fown = network.nwbuf[i];
+                            deown = network.nwbuf[nout+i];
+                            network.chunks[iderror+ntotal-nout+i,k] = (-v+deown*fown+deown*(net-fown))*fown/math.sqr(net);
+                        }
+                    }
+                }
+            }
+            else
+            {
+                
+                //
+                // Normal output, regression network
+                //
+                // For each K = 0..CSize-1 do:
+                // 1. calculate dError/dOut and place it to the second block of Chunks
+                //
+                for(i=0; i<=nout-1; i++)
+                {
+                    for(j=0; j<=csize-1; j++)
+                    {
+                        v = network.chunks[ntotal-nout+i,j]*network.columnsigmas[nin+i]+network.columnmeans[nin+i]-xy[cstart+j,nin+i];
+                        network.chunks[iderror+ntotal-nout+i,j] = v*network.columnsigmas[nin+i];
+                        e = e+math.sqr(v)/2;
+                    }
+                }
+            }
+            
+            //
+            // Backpropagation
+            //
+            for(i=ntotal-1; i>=0; i--)
+            {
+                
+                //
+                // Extract info
+                //
+                offs = istart+i*nfieldwidth;
+                if( network.structinfo[offs+0]>0 || network.structinfo[offs+0]==-5 )
+                {
+                    
+                    //
+                    // Activation function
+                    //
+                    n1 = network.structinfo[offs+2];
+                    for(k=0; k<=csize-1; k++)
+                    {
+                        network.chunks[iderror+i,k] = network.chunks[iderror+i,k]*network.chunks[idfdnet+i,k];
+                    }
+                    for(i_=0; i_<=csize-1;i_++)
+                    {
+                        network.chunks[iderror+n1,i_] = network.chunks[iderror+n1,i_] + network.chunks[iderror+i,i_];
+                    }
+                    continue;
+                }
+                if( network.structinfo[offs+0]==0 )
+                {
+                    
+                    //
+                    // "Normal" activation function
+                    //
+                    n1 = network.structinfo[offs+2];
+                    n2 = n1+network.structinfo[offs+1]-1;
+                    w1 = network.structinfo[offs+3];
+                    w2 = w1+network.structinfo[offs+1]-1;
+                    for(j=w1; j<=w2; j++)
+                    {
+                        v = 0.0;
+                        for(i_=0; i_<=csize-1;i_++)
+                        {
+                            v += network.chunks[n1+j-w1,i_]*network.chunks[iderror+i,i_];
+                        }
+                        grad[j] = grad[j]+v;
+                    }
+                    for(j=n1; j<=n2; j++)
+                    {
+                        v = network.weights[w1+j-n1];
+                        for(i_=0; i_<=csize-1;i_++)
+                        {
+                            network.chunks[iderror+j,i_] = network.chunks[iderror+j,i_] + v*network.chunks[iderror+i,i_];
+                        }
+                    }
+                    continue;
+                }
+                if( network.structinfo[offs+0]<0 )
+                {
+                    bflag = false;
+                    if( (network.structinfo[offs+0]==-2 || network.structinfo[offs+0]==-3) || network.structinfo[offs+0]==-4 )
+                    {
+                        
+                        //
+                        // Special neuron type, no back-propagation required
+                        //
+                        bflag = true;
+                    }
+                    alglib.ap.assert(bflag, "MLPInternalCalculateGradient: unknown neuron type!");
+                    continue;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Returns T*Ln(T/Z), guarded against overflow/underflow.
+        Internal subroutine.
+        *************************************************************************/
+        private static double safecrossentropy(double t,
+            double z)
+        {
+            double result = 0;
+            double r = 0;
+
+            if( (double)(t)==(double)(0) )
+            {
+                result = 0;
+            }
+            else
+            {
+                if( (double)(Math.Abs(z))>(double)(1) )
+                {
+                    
+                    //
+                    // Shouldn't be the case with softmax,
+                    // but we just want to be sure.
+                    //
+                    if( (double)(t/z)==(double)(0) )
+                    {
+                        r = math.minrealnumber;
+                    }
+                    else
+                    {
+                        r = t/z;
+                    }
+                }
+                else
+                {
+                    
+                    //
+                    // Normal case
+                    //
+                    if( (double)(z)==(double)(0) || (double)(Math.Abs(t))>=(double)(math.maxrealnumber*Math.Abs(z)) )
+                    {
+                        r = math.maxrealnumber;
+                    }
+                    else
+                    {
+                        r = t/z;
+                    }
+                }
+                result = t*Math.Log(r);
+            }
+            return result;
+        }
+
+
+    }
+    public class logit
+    {
+        public class logitmodel
+        {
+            public double[] w;
+            public logitmodel()
+            {
+                w = new double[0];
+            }
+        };
+
+
+        public class logitmcstate
+        {
+            public bool brackt;
+            public bool stage1;
+            public int infoc;
+            public double dg;
+            public double dgm;
+            public double dginit;
+            public double dgtest;
+            public double dgx;
+            public double dgxm;
+            public double dgy;
+            public double dgym;
+            public double finit;
+            public double ftest1;
+            public double fm;
+            public double fx;
+            public double fxm;
+            public double fy;
+            public double fym;
+            public double stx;
+            public double sty;
+            public double stmin;
+            public double stmax;
+            public double width;
+            public double width1;
+            public double xtrapf;
+        };
+
+
+        /*************************************************************************
+        MNLReport structure contains information about training process:
+        * NGrad     -   number of gradient calculations
+        * NHess     -   number of Hessian calculations
+        *************************************************************************/
+        public class mnlreport
+        {
+            public int ngrad;
+            public int nhess;
+        };
+
+
+
+
+        public const double xtol = 100*math.machineepsilon;
+        public const double ftol = 0.0001;
+        public const double gtol = 0.3;
+        public const int maxfev = 20;
+        public const double stpmin = 1.0E-2;
+        public const double stpmax = 1.0E5;
+        public const int logitvnum = 6;
+
+
+        /*************************************************************************
+        This subroutine trains logit model.
+
+        INPUT PARAMETERS:
+            XY          -   training set, array[0..NPoints-1,0..NVars]
+                            First NVars columns store values of independent
+                            variables, next column stores number of class (from 0
+                            to NClasses-1) which dataset element belongs to. Fractional
+                            values are rounded to nearest integer.
+            NPoints     -   training set size, NPoints>=1
+            NVars       -   number of independent variables, NVars>=1
+            NClasses    -   number of classes, NClasses>=2
+
+        OUTPUT PARAMETERS:
+            Info        -   return code:
+                            * -2, if there is a point with class number
+                                  outside of [0..NClasses-1].
+                            * -1, if incorrect parameters was passed
+                                  (NPoints<NVars+2, NVars<1, NClasses<2).
+                            *  1, if task has been solved
+            LM          -   model built
+            Rep         -   training report
+
+          -- ALGLIB --
+             Copyright 10.09.2008 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mnltrainh(double[,] xy,
+            int npoints,
+            int nvars,
+            int nclasses,
+            ref int info,
+            logitmodel lm,
+            mnlreport rep)
+        {
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int ssize = 0;
+            bool allsame = new bool();
+            int offs = 0;
+            double threshold = 0;
+            double wminstep = 0;
+            double decay = 0;
+            int wdim = 0;
+            int expoffs = 0;
+            double v = 0;
+            double s = 0;
+            mlpbase.multilayerperceptron network = new mlpbase.multilayerperceptron();
+            int nin = 0;
+            int nout = 0;
+            int wcount = 0;
+            double e = 0;
+            double[] g = new double[0];
+            double[,] h = new double[0,0];
+            bool spd = new bool();
+            double[] x = new double[0];
+            double[] y = new double[0];
+            double[] wbase = new double[0];
+            double wstep = 0;
+            double[] wdir = new double[0];
+            double[] work = new double[0];
+            int mcstage = 0;
+            logitmcstate mcstate = new logitmcstate();
+            int mcinfo = 0;
+            int mcnfev = 0;
+            int solverinfo = 0;
+            densesolver.densesolverreport solverrep = new densesolver.densesolverreport();
+            int i_ = 0;
+            int i1_ = 0;
+
+            info = 0;
+
+            threshold = 1000*math.machineepsilon;
+            wminstep = 0.001;
+            decay = 0.001;
+            
+            //
+            // Test for inputs
+            //
+            if( (npoints<nvars+2 || nvars<1) || nclasses<2 )
+            {
+                info = -1;
+                return;
+            }
+            for(i=0; i<=npoints-1; i++)
+            {
+                if( (int)Math.Round(xy[i,nvars])<0 || (int)Math.Round(xy[i,nvars])>=nclasses )
+                {
+                    info = -2;
+                    return;
+                }
+            }
+            info = 1;
+            
+            //
+            // Initialize data
+            //
+            rep.ngrad = 0;
+            rep.nhess = 0;
+            
+            //
+            // Allocate array
+            //
+            wdim = (nvars+1)*(nclasses-1);
+            offs = 5;
+            expoffs = offs+wdim;
+            ssize = 5+(nvars+1)*(nclasses-1)+nclasses;
+            lm.w = new double[ssize-1+1];
+            lm.w[0] = ssize;
+            lm.w[1] = logitvnum;
+            lm.w[2] = nvars;
+            lm.w[3] = nclasses;
+            lm.w[4] = offs;
+            
+            //
+            // Degenerate case: all outputs are equal
+            //
+            allsame = true;
+            for(i=1; i<=npoints-1; i++)
+            {
+                if( (int)Math.Round(xy[i,nvars])!=(int)Math.Round(xy[i-1,nvars]) )
+                {
+                    allsame = false;
+                }
+            }
+            if( allsame )
+            {
+                for(i=0; i<=(nvars+1)*(nclasses-1)-1; i++)
+                {
+                    lm.w[offs+i] = 0;
+                }
+                v = -(2*Math.Log(math.minrealnumber));
+                k = (int)Math.Round(xy[0,nvars]);
+                if( k==nclasses-1 )
+                {
+                    for(i=0; i<=nclasses-2; i++)
+                    {
+                        lm.w[offs+i*(nvars+1)+nvars] = -v;
+                    }
+                }
+                else
+                {
+                    for(i=0; i<=nclasses-2; i++)
+                    {
+                        if( i==k )
+                        {
+                            lm.w[offs+i*(nvars+1)+nvars] = v;
+                        }
+                        else
+                        {
+                            lm.w[offs+i*(nvars+1)+nvars] = 0;
+                        }
+                    }
+                }
+                return;
+            }
+            
+            //
+            // General case.
+            // Prepare task and network. Allocate space.
+            //
+            mlpbase.mlpcreatec0(nvars, nclasses, network);
+            mlpbase.mlpinitpreprocessor(network, xy, npoints);
+            mlpbase.mlpproperties(network, ref nin, ref nout, ref wcount);
+            for(i=0; i<=wcount-1; i++)
+            {
+                network.weights[i] = (2*math.randomreal()-1)/nvars;
+            }
+            g = new double[wcount-1+1];
+            h = new double[wcount-1+1, wcount-1+1];
+            wbase = new double[wcount-1+1];
+            wdir = new double[wcount-1+1];
+            work = new double[wcount-1+1];
+            
+            //
+            // First stage: optimize in gradient direction.
+            //
+            for(k=0; k<=wcount/3+10; k++)
+            {
+                
+                //
+                // Calculate gradient in starting point
+                //
+                mlpbase.mlpgradnbatch(network, xy, npoints, ref e, ref g);
+                v = 0.0;
+                for(i_=0; i_<=wcount-1;i_++)
+                {
+                    v += network.weights[i_]*network.weights[i_];
+                }
+                e = e+0.5*decay*v;
+                for(i_=0; i_<=wcount-1;i_++)
+                {
+                    g[i_] = g[i_] + decay*network.weights[i_];
+                }
+                rep.ngrad = rep.ngrad+1;
+                
+                //
+                // Setup optimization scheme
+                //
+                for(i_=0; i_<=wcount-1;i_++)
+                {
+                    wdir[i_] = -g[i_];
+                }
+                v = 0.0;
+                for(i_=0; i_<=wcount-1;i_++)
+                {
+                    v += wdir[i_]*wdir[i_];
+                }
+                wstep = Math.Sqrt(v);
+                v = 1/Math.Sqrt(v);
+                for(i_=0; i_<=wcount-1;i_++)
+                {
+                    wdir[i_] = v*wdir[i_];
+                }
+                mcstage = 0;
+                mnlmcsrch(wcount, ref network.weights, ref e, ref g, wdir, ref wstep, ref mcinfo, ref mcnfev, ref work, mcstate, ref mcstage);
+                while( mcstage!=0 )
+                {
+                    mlpbase.mlpgradnbatch(network, xy, npoints, ref e, ref g);
+                    v = 0.0;
+                    for(i_=0; i_<=wcount-1;i_++)
+                    {
+                        v += network.weights[i_]*network.weights[i_];
+                    }
+                    e = e+0.5*decay*v;
+                    for(i_=0; i_<=wcount-1;i_++)
+                    {
+                        g[i_] = g[i_] + decay*network.weights[i_];
+                    }
+                    rep.ngrad = rep.ngrad+1;
+                    mnlmcsrch(wcount, ref network.weights, ref e, ref g, wdir, ref wstep, ref mcinfo, ref mcnfev, ref work, mcstate, ref mcstage);
+                }
+            }
+            
+            //
+            // Second stage: use Hessian when we are close to the minimum
+            //
+            while( true )
+            {
+                
+                //
+                // Calculate and update E/G/H
+                //
+                mlpbase.mlphessiannbatch(network, xy, npoints, ref e, ref g, ref h);
+                v = 0.0;
+                for(i_=0; i_<=wcount-1;i_++)
+                {
+                    v += network.weights[i_]*network.weights[i_];
+                }
+                e = e+0.5*decay*v;
+                for(i_=0; i_<=wcount-1;i_++)
+                {
+                    g[i_] = g[i_] + decay*network.weights[i_];
+                }
+                for(k=0; k<=wcount-1; k++)
+                {
+                    h[k,k] = h[k,k]+decay;
+                }
+                rep.nhess = rep.nhess+1;
+                
+                //
+                // Select step direction
+                // NOTE: it is important to use lower-triangle Cholesky
+                // factorization since it is much faster than higher-triangle version.
+                //
+                spd = trfac.spdmatrixcholesky(ref h, wcount, false);
+                densesolver.spdmatrixcholeskysolve(h, wcount, false, g, ref solverinfo, solverrep, ref wdir);
+                spd = solverinfo>0;
+                if( spd )
+                {
+                    
+                    //
+                    // H is positive definite.
+                    // Step in Newton direction.
+                    //
+                    for(i_=0; i_<=wcount-1;i_++)
+                    {
+                        wdir[i_] = -1*wdir[i_];
+                    }
+                    spd = true;
+                }
+                else
+                {
+                    
+                    //
+                    // H is indefinite.
+                    // Step in gradient direction.
+                    //
+                    for(i_=0; i_<=wcount-1;i_++)
+                    {
+                        wdir[i_] = -g[i_];
+                    }
+                    spd = false;
+                }
+                
+                //
+                // Optimize in WDir direction
+                //
+                v = 0.0;
+                for(i_=0; i_<=wcount-1;i_++)
+                {
+                    v += wdir[i_]*wdir[i_];
+                }
+                wstep = Math.Sqrt(v);
+                v = 1/Math.Sqrt(v);
+                for(i_=0; i_<=wcount-1;i_++)
+                {
+                    wdir[i_] = v*wdir[i_];
+                }
+                mcstage = 0;
+                mnlmcsrch(wcount, ref network.weights, ref e, ref g, wdir, ref wstep, ref mcinfo, ref mcnfev, ref work, mcstate, ref mcstage);
+                while( mcstage!=0 )
+                {
+                    mlpbase.mlpgradnbatch(network, xy, npoints, ref e, ref g);
+                    v = 0.0;
+                    for(i_=0; i_<=wcount-1;i_++)
+                    {
+                        v += network.weights[i_]*network.weights[i_];
+                    }
+                    e = e+0.5*decay*v;
+                    for(i_=0; i_<=wcount-1;i_++)
+                    {
+                        g[i_] = g[i_] + decay*network.weights[i_];
+                    }
+                    rep.ngrad = rep.ngrad+1;
+                    mnlmcsrch(wcount, ref network.weights, ref e, ref g, wdir, ref wstep, ref mcinfo, ref mcnfev, ref work, mcstate, ref mcstage);
+                }
+                if( spd && ((mcinfo==2 || mcinfo==4) || mcinfo==6) )
+                {
+                    break;
+                }
+            }
+            
+            //
+            // Convert from NN format to MNL format
+            //
+            i1_ = (0) - (offs);
+            for(i_=offs; i_<=offs+wcount-1;i_++)
+            {
+                lm.w[i_] = network.weights[i_+i1_];
+            }
+            for(k=0; k<=nvars-1; k++)
+            {
+                for(i=0; i<=nclasses-2; i++)
+                {
+                    s = network.columnsigmas[k];
+                    if( (double)(s)==(double)(0) )
+                    {
+                        s = 1;
+                    }
+                    j = offs+(nvars+1)*i;
+                    v = lm.w[j+k];
+                    lm.w[j+k] = v/s;
+                    lm.w[j+nvars] = lm.w[j+nvars]+v*network.columnmeans[k]/s;
+                }
+            }
+            for(k=0; k<=nclasses-2; k++)
+            {
+                lm.w[offs+(nvars+1)*k+nvars] = -lm.w[offs+(nvars+1)*k+nvars];
+            }
+        }
+
+
+        /*************************************************************************
+        Procesing
+
+        INPUT PARAMETERS:
+            LM      -   logit model, passed by non-constant reference
+                        (some fields of structure are used as temporaries
+                        when calculating model output).
+            X       -   input vector,  array[0..NVars-1].
+            Y       -   (possibly) preallocated buffer; if size of Y is less than
+                        NClasses, it will be reallocated.If it is large enough, it
+                        is NOT reallocated, so we can save some time on reallocation.
+
+        OUTPUT PARAMETERS:
+            Y       -   result, array[0..NClasses-1]
+                        Vector of posterior probabilities for classification task.
+
+          -- ALGLIB --
+             Copyright 10.09.2008 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mnlprocess(logitmodel lm,
+            double[] x,
+            ref double[] y)
+        {
+            int nvars = 0;
+            int nclasses = 0;
+            int offs = 0;
+            int i = 0;
+            int i1 = 0;
+            double s = 0;
+
+            alglib.ap.assert((double)(lm.w[1])==(double)(logitvnum), "MNLProcess: unexpected model version");
+            nvars = (int)Math.Round(lm.w[2]);
+            nclasses = (int)Math.Round(lm.w[3]);
+            offs = (int)Math.Round(lm.w[4]);
+            mnliexp(ref lm.w, x);
+            s = 0;
+            i1 = offs+(nvars+1)*(nclasses-1);
+            for(i=i1; i<=i1+nclasses-1; i++)
+            {
+                s = s+lm.w[i];
+            }
+            if( alglib.ap.len(y)<nclasses )
+            {
+                y = new double[nclasses];
+            }
+            for(i=0; i<=nclasses-1; i++)
+            {
+                y[i] = lm.w[i1+i]/s;
+            }
+        }
+
+
+        /*************************************************************************
+        'interactive'  variant  of  MNLProcess  for  languages  like  Python which
+        support constructs like "Y = MNLProcess(LM,X)" and interactive mode of the
+        interpreter
+
+        This function allocates new array on each call,  so  it  is  significantly
+        slower than its 'non-interactive' counterpart, but it is  more  convenient
+        when you call it from command line.
+
+          -- ALGLIB --
+             Copyright 10.09.2008 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mnlprocessi(logitmodel lm,
+            double[] x,
+            ref double[] y)
+        {
+            y = new double[0];
+
+            mnlprocess(lm, x, ref y);
+        }
+
+
+        /*************************************************************************
+        Unpacks coefficients of logit model. Logit model have form:
+
+            P(class=i) = S(i) / (S(0) + S(1) + ... +S(M-1))
+                  S(i) = Exp(A[i,0]*X[0] + ... + A[i,N-1]*X[N-1] + A[i,N]), when i<M-1
+                S(M-1) = 1
+
+        INPUT PARAMETERS:
+            LM          -   logit model in ALGLIB format
+
+        OUTPUT PARAMETERS:
+            V           -   coefficients, array[0..NClasses-2,0..NVars]
+            NVars       -   number of independent variables
+            NClasses    -   number of classes
+
+          -- ALGLIB --
+             Copyright 10.09.2008 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mnlunpack(logitmodel lm,
+            ref double[,] a,
+            ref int nvars,
+            ref int nclasses)
+        {
+            int offs = 0;
+            int i = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            a = new double[0,0];
+            nvars = 0;
+            nclasses = 0;
+
+            alglib.ap.assert((double)(lm.w[1])==(double)(logitvnum), "MNLUnpack: unexpected model version");
+            nvars = (int)Math.Round(lm.w[2]);
+            nclasses = (int)Math.Round(lm.w[3]);
+            offs = (int)Math.Round(lm.w[4]);
+            a = new double[nclasses-2+1, nvars+1];
+            for(i=0; i<=nclasses-2; i++)
+            {
+                i1_ = (offs+i*(nvars+1)) - (0);
+                for(i_=0; i_<=nvars;i_++)
+                {
+                    a[i,i_] = lm.w[i_+i1_];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        "Packs" coefficients and creates logit model in ALGLIB format (MNLUnpack
+        reversed).
+
+        INPUT PARAMETERS:
+            A           -   model (see MNLUnpack)
+            NVars       -   number of independent variables
+            NClasses    -   number of classes
+
+        OUTPUT PARAMETERS:
+            LM          -   logit model.
+
+          -- ALGLIB --
+             Copyright 10.09.2008 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mnlpack(double[,] a,
+            int nvars,
+            int nclasses,
+            logitmodel lm)
+        {
+            int offs = 0;
+            int i = 0;
+            int wdim = 0;
+            int ssize = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            wdim = (nvars+1)*(nclasses-1);
+            offs = 5;
+            ssize = 5+(nvars+1)*(nclasses-1)+nclasses;
+            lm.w = new double[ssize-1+1];
+            lm.w[0] = ssize;
+            lm.w[1] = logitvnum;
+            lm.w[2] = nvars;
+            lm.w[3] = nclasses;
+            lm.w[4] = offs;
+            for(i=0; i<=nclasses-2; i++)
+            {
+                i1_ = (0) - (offs+i*(nvars+1));
+                for(i_=offs+i*(nvars+1); i_<=offs+i*(nvars+1)+nvars;i_++)
+                {
+                    lm.w[i_] = a[i,i_+i1_];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Copying of LogitModel strucure
+
+        INPUT PARAMETERS:
+            LM1 -   original
+
+        OUTPUT PARAMETERS:
+            LM2 -   copy
+
+          -- ALGLIB --
+             Copyright 15.03.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mnlcopy(logitmodel lm1,
+            logitmodel lm2)
+        {
+            int k = 0;
+            int i_ = 0;
+
+            k = (int)Math.Round(lm1.w[0]);
+            lm2.w = new double[k-1+1];
+            for(i_=0; i_<=k-1;i_++)
+            {
+                lm2.w[i_] = lm1.w[i_];
+            }
+        }
+
+
+        /*************************************************************************
+        Average cross-entropy (in bits per element) on the test set
+
+        INPUT PARAMETERS:
+            LM      -   logit model
+            XY      -   test set
+            NPoints -   test set size
+
+        RESULT:
+            CrossEntropy/(NPoints*ln(2)).
+
+          -- ALGLIB --
+             Copyright 10.09.2008 by Bochkanov Sergey
+        *************************************************************************/
+        public static double mnlavgce(logitmodel lm,
+            double[,] xy,
+            int npoints)
+        {
+            double result = 0;
+            int nvars = 0;
+            int nclasses = 0;
+            int i = 0;
+            double[] workx = new double[0];
+            double[] worky = new double[0];
+            int i_ = 0;
+
+            alglib.ap.assert((double)(lm.w[1])==(double)(logitvnum), "MNLClsError: unexpected model version");
+            nvars = (int)Math.Round(lm.w[2]);
+            nclasses = (int)Math.Round(lm.w[3]);
+            workx = new double[nvars-1+1];
+            worky = new double[nclasses-1+1];
+            result = 0;
+            for(i=0; i<=npoints-1; i++)
+            {
+                alglib.ap.assert((int)Math.Round(xy[i,nvars])>=0 && (int)Math.Round(xy[i,nvars])<nclasses, "MNLAvgCE: incorrect class number!");
+                
+                //
+                // Process
+                //
+                for(i_=0; i_<=nvars-1;i_++)
+                {
+                    workx[i_] = xy[i,i_];
+                }
+                mnlprocess(lm, workx, ref worky);
+                if( (double)(worky[(int)Math.Round(xy[i,nvars])])>(double)(0) )
+                {
+                    result = result-Math.Log(worky[(int)Math.Round(xy[i,nvars])]);
+                }
+                else
+                {
+                    result = result-Math.Log(math.minrealnumber);
+                }
+            }
+            result = result/(npoints*Math.Log(2));
+            return result;
+        }
+
+
+        /*************************************************************************
+        Relative classification error on the test set
+
+        INPUT PARAMETERS:
+            LM      -   logit model
+            XY      -   test set
+            NPoints -   test set size
+
+        RESULT:
+            percent of incorrectly classified cases.
+
+          -- ALGLIB --
+             Copyright 10.09.2008 by Bochkanov Sergey
+        *************************************************************************/
+        public static double mnlrelclserror(logitmodel lm,
+            double[,] xy,
+            int npoints)
+        {
+            double result = 0;
+
+            result = (double)mnlclserror(lm, xy, npoints)/(double)npoints;
+            return result;
+        }
+
+
+        /*************************************************************************
+        RMS error on the test set
+
+        INPUT PARAMETERS:
+            LM      -   logit model
+            XY      -   test set
+            NPoints -   test set size
+
+        RESULT:
+            root mean square error (error when estimating posterior probabilities).
+
+          -- ALGLIB --
+             Copyright 30.08.2008 by Bochkanov Sergey
+        *************************************************************************/
+        public static double mnlrmserror(logitmodel lm,
+            double[,] xy,
+            int npoints)
+        {
+            double result = 0;
+            double relcls = 0;
+            double avgce = 0;
+            double rms = 0;
+            double avg = 0;
+            double avgrel = 0;
+
+            alglib.ap.assert((int)Math.Round(lm.w[1])==logitvnum, "MNLRMSError: Incorrect MNL version!");
+            mnlallerrors(lm, xy, npoints, ref relcls, ref avgce, ref rms, ref avg, ref avgrel);
+            result = rms;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Average error on the test set
+
+        INPUT PARAMETERS:
+            LM      -   logit model
+            XY      -   test set
+            NPoints -   test set size
+
+        RESULT:
+            average error (error when estimating posterior probabilities).
+
+          -- ALGLIB --
+             Copyright 30.08.2008 by Bochkanov Sergey
+        *************************************************************************/
+        public static double mnlavgerror(logitmodel lm,
+            double[,] xy,
+            int npoints)
+        {
+            double result = 0;
+            double relcls = 0;
+            double avgce = 0;
+            double rms = 0;
+            double avg = 0;
+            double avgrel = 0;
+
+            alglib.ap.assert((int)Math.Round(lm.w[1])==logitvnum, "MNLRMSError: Incorrect MNL version!");
+            mnlallerrors(lm, xy, npoints, ref relcls, ref avgce, ref rms, ref avg, ref avgrel);
+            result = avg;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Average relative error on the test set
+
+        INPUT PARAMETERS:
+            LM      -   logit model
+            XY      -   test set
+            NPoints -   test set size
+
+        RESULT:
+            average relative error (error when estimating posterior probabilities).
+
+          -- ALGLIB --
+             Copyright 30.08.2008 by Bochkanov Sergey
+        *************************************************************************/
+        public static double mnlavgrelerror(logitmodel lm,
+            double[,] xy,
+            int ssize)
+        {
+            double result = 0;
+            double relcls = 0;
+            double avgce = 0;
+            double rms = 0;
+            double avg = 0;
+            double avgrel = 0;
+
+            alglib.ap.assert((int)Math.Round(lm.w[1])==logitvnum, "MNLRMSError: Incorrect MNL version!");
+            mnlallerrors(lm, xy, ssize, ref relcls, ref avgce, ref rms, ref avg, ref avgrel);
+            result = avgrel;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Classification error on test set = MNLRelClsError*NPoints
+
+          -- ALGLIB --
+             Copyright 10.09.2008 by Bochkanov Sergey
+        *************************************************************************/
+        public static int mnlclserror(logitmodel lm,
+            double[,] xy,
+            int npoints)
+        {
+            int result = 0;
+            int nvars = 0;
+            int nclasses = 0;
+            int i = 0;
+            int j = 0;
+            double[] workx = new double[0];
+            double[] worky = new double[0];
+            int nmax = 0;
+            int i_ = 0;
+
+            alglib.ap.assert((double)(lm.w[1])==(double)(logitvnum), "MNLClsError: unexpected model version");
+            nvars = (int)Math.Round(lm.w[2]);
+            nclasses = (int)Math.Round(lm.w[3]);
+            workx = new double[nvars-1+1];
+            worky = new double[nclasses-1+1];
+            result = 0;
+            for(i=0; i<=npoints-1; i++)
+            {
+                
+                //
+                // Process
+                //
+                for(i_=0; i_<=nvars-1;i_++)
+                {
+                    workx[i_] = xy[i,i_];
+                }
+                mnlprocess(lm, workx, ref worky);
+                
+                //
+                // Logit version of the answer
+                //
+                nmax = 0;
+                for(j=0; j<=nclasses-1; j++)
+                {
+                    if( (double)(worky[j])>(double)(worky[nmax]) )
+                    {
+                        nmax = j;
+                    }
+                }
+                
+                //
+                // compare
+                //
+                if( nmax!=(int)Math.Round(xy[i,nvars]) )
+                {
+                    result = result+1;
+                }
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        Internal subroutine. Places exponents of the anti-overflow shifted
+        internal linear outputs into the service part of the W array.
+        *************************************************************************/
+        private static void mnliexp(ref double[] w,
+            double[] x)
+        {
+            int nvars = 0;
+            int nclasses = 0;
+            int offs = 0;
+            int i = 0;
+            int i1 = 0;
+            double v = 0;
+            double mx = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            alglib.ap.assert((double)(w[1])==(double)(logitvnum), "LOGIT: unexpected model version");
+            nvars = (int)Math.Round(w[2]);
+            nclasses = (int)Math.Round(w[3]);
+            offs = (int)Math.Round(w[4]);
+            i1 = offs+(nvars+1)*(nclasses-1);
+            for(i=0; i<=nclasses-2; i++)
+            {
+                i1_ = (0)-(offs+i*(nvars+1));
+                v = 0.0;
+                for(i_=offs+i*(nvars+1); i_<=offs+i*(nvars+1)+nvars-1;i_++)
+                {
+                    v += w[i_]*x[i_+i1_];
+                }
+                w[i1+i] = v+w[offs+i*(nvars+1)+nvars];
+            }
+            w[i1+nclasses-1] = 0;
+            mx = 0;
+            for(i=i1; i<=i1+nclasses-1; i++)
+            {
+                mx = Math.Max(mx, w[i]);
+            }
+            for(i=i1; i<=i1+nclasses-1; i++)
+            {
+                w[i] = Math.Exp(w[i]-mx);
+            }
+        }
+
+
+        /*************************************************************************
+        Calculation of all types of errors
+
+          -- ALGLIB --
+             Copyright 30.08.2008 by Bochkanov Sergey
+        *************************************************************************/
+        private static void mnlallerrors(logitmodel lm,
+            double[,] xy,
+            int npoints,
+            ref double relcls,
+            ref double avgce,
+            ref double rms,
+            ref double avg,
+            ref double avgrel)
+        {
+            int nvars = 0;
+            int nclasses = 0;
+            int i = 0;
+            double[] buf = new double[0];
+            double[] workx = new double[0];
+            double[] y = new double[0];
+            double[] dy = new double[0];
+            int i_ = 0;
+
+            relcls = 0;
+            avgce = 0;
+            rms = 0;
+            avg = 0;
+            avgrel = 0;
+
+            alglib.ap.assert((int)Math.Round(lm.w[1])==logitvnum, "MNL unit: Incorrect MNL version!");
+            nvars = (int)Math.Round(lm.w[2]);
+            nclasses = (int)Math.Round(lm.w[3]);
+            workx = new double[nvars-1+1];
+            y = new double[nclasses-1+1];
+            dy = new double[0+1];
+            bdss.dserrallocate(nclasses, ref buf);
+            for(i=0; i<=npoints-1; i++)
+            {
+                for(i_=0; i_<=nvars-1;i_++)
+                {
+                    workx[i_] = xy[i,i_];
+                }
+                mnlprocess(lm, workx, ref y);
+                dy[0] = xy[i,nvars];
+                bdss.dserraccumulate(ref buf, y, dy);
+            }
+            bdss.dserrfinish(ref buf);
+            relcls = buf[0];
+            avgce = buf[1];
+            rms = buf[2];
+            avg = buf[3];
+            avgrel = buf[4];
+        }
+
+
+        /*************************************************************************
+        THE  PURPOSE  OF  MCSRCH  IS  TO  FIND A STEP WHICH SATISFIES A SUFFICIENT
+        DECREASE CONDITION AND A CURVATURE CONDITION.
+
+        AT EACH STAGE THE SUBROUTINE  UPDATES  AN  INTERVAL  OF  UNCERTAINTY  WITH
+        ENDPOINTS  STX  AND  STY.  THE INTERVAL OF UNCERTAINTY IS INITIALLY CHOSEN
+        SO THAT IT CONTAINS A MINIMIZER OF THE MODIFIED FUNCTION
+
+            F(X+STP*S) - F(X) - FTOL*STP*(GRADF(X)'S).
+
+        IF  A STEP  IS OBTAINED FOR  WHICH THE MODIFIED FUNCTION HAS A NONPOSITIVE
+        FUNCTION  VALUE  AND  NONNEGATIVE  DERIVATIVE,   THEN   THE   INTERVAL  OF
+        UNCERTAINTY IS CHOSEN SO THAT IT CONTAINS A MINIMIZER OF F(X+STP*S).
+
+        THE  ALGORITHM  IS  DESIGNED TO FIND A STEP WHICH SATISFIES THE SUFFICIENT
+        DECREASE CONDITION
+
+            F(X+STP*S) .LE. F(X) + FTOL*STP*(GRADF(X)'S),
+
+        AND THE CURVATURE CONDITION
+
+            ABS(GRADF(X+STP*S)'S)) .LE. GTOL*ABS(GRADF(X)'S).
+
+        IF  FTOL  IS  LESS  THAN GTOL AND IF, FOR EXAMPLE, THE FUNCTION IS BOUNDED
+        BELOW,  THEN  THERE  IS  ALWAYS  A  STEP  WHICH SATISFIES BOTH CONDITIONS.
+        IF  NO  STEP  CAN BE FOUND  WHICH  SATISFIES  BOTH  CONDITIONS,  THEN  THE
+        ALGORITHM  USUALLY STOPS  WHEN  ROUNDING ERRORS  PREVENT FURTHER PROGRESS.
+        IN THIS CASE STP ONLY SATISFIES THE SUFFICIENT DECREASE CONDITION.
+
+        PARAMETERS DESCRIPRION
+
+        N IS A POSITIVE INTEGER INPUT VARIABLE SET TO THE NUMBER OF VARIABLES.
+
+        X IS  AN  ARRAY  OF  LENGTH N. ON INPUT IT MUST CONTAIN THE BASE POINT FOR
+        THE LINE SEARCH. ON OUTPUT IT CONTAINS X+STP*S.
+
+        F IS  A  VARIABLE. ON INPUT IT MUST CONTAIN THE VALUE OF F AT X. ON OUTPUT
+        IT CONTAINS THE VALUE OF F AT X + STP*S.
+
+        G IS AN ARRAY OF LENGTH N. ON INPUT IT MUST CONTAIN THE GRADIENT OF F AT X.
+        ON OUTPUT IT CONTAINS THE GRADIENT OF F AT X + STP*S.
+
+        S IS AN INPUT ARRAY OF LENGTH N WHICH SPECIFIES THE SEARCH DIRECTION.
+
+        STP  IS  A NONNEGATIVE VARIABLE. ON INPUT STP CONTAINS AN INITIAL ESTIMATE
+        OF A SATISFACTORY STEP. ON OUTPUT STP CONTAINS THE FINAL ESTIMATE.
+
+        FTOL AND GTOL ARE NONNEGATIVE INPUT VARIABLES. TERMINATION OCCURS WHEN THE
+        SUFFICIENT DECREASE CONDITION AND THE DIRECTIONAL DERIVATIVE CONDITION ARE
+        SATISFIED.
+
+        XTOL IS A NONNEGATIVE INPUT VARIABLE. TERMINATION OCCURS WHEN THE RELATIVE
+        WIDTH OF THE INTERVAL OF UNCERTAINTY IS AT MOST XTOL.
+
+        STPMIN AND STPMAX ARE NONNEGATIVE INPUT VARIABLES WHICH SPECIFY LOWER  AND
+        UPPER BOUNDS FOR THE STEP.
+
+        MAXFEV IS A POSITIVE INTEGER INPUT VARIABLE. TERMINATION OCCURS WHEN THE
+        NUMBER OF CALLS TO FCN IS AT LEAST MAXFEV BY THE END OF AN ITERATION.
+
+        INFO IS AN INTEGER OUTPUT VARIABLE SET AS FOLLOWS:
+            INFO = 0  IMPROPER INPUT PARAMETERS.
+
+            INFO = 1  THE SUFFICIENT DECREASE CONDITION AND THE
+                      DIRECTIONAL DERIVATIVE CONDITION HOLD.
+
+            INFO = 2  RELATIVE WIDTH OF THE INTERVAL OF UNCERTAINTY
+                      IS AT MOST XTOL.
+
+            INFO = 3  NUMBER OF CALLS TO FCN HAS REACHED MAXFEV.
+
+            INFO = 4  THE STEP IS AT THE LOWER BOUND STPMIN.
+
+            INFO = 5  THE STEP IS AT THE UPPER BOUND STPMAX.
+
+            INFO = 6  ROUNDING ERRORS PREVENT FURTHER PROGRESS.
+                      THERE MAY NOT BE A STEP WHICH SATISFIES THE
+                      SUFFICIENT DECREASE AND CURVATURE CONDITIONS.
+                      TOLERANCES MAY BE TOO SMALL.
+
+        NFEV IS AN INTEGER OUTPUT VARIABLE SET TO THE NUMBER OF CALLS TO FCN.
+
+        WA IS A WORK ARRAY OF LENGTH N.
+
+        ARGONNE NATIONAL LABORATORY. MINPACK PROJECT. JUNE 1983
+        JORGE J. MORE', DAVID J. THUENTE
+        *************************************************************************/
+        private static void mnlmcsrch(int n,
+            ref double[] x,
+            ref double f,
+            ref double[] g,
+            double[] s,
+            ref double stp,
+            ref int info,
+            ref int nfev,
+            ref double[] wa,
+            logitmcstate state,
+            ref int stage)
+        {
+            double v = 0;
+            double p5 = 0;
+            double p66 = 0;
+            double zero = 0;
+            int i_ = 0;
+
+            
+            //
+            // init
+            //
+            p5 = 0.5;
+            p66 = 0.66;
+            state.xtrapf = 4.0;
+            zero = 0;
+            
+            //
+            // Main cycle
+            //
+            while( true )
+            {
+                if( stage==0 )
+                {
+                    
+                    //
+                    // NEXT
+                    //
+                    stage = 2;
+                    continue;
+                }
+                if( stage==2 )
+                {
+                    state.infoc = 1;
+                    info = 0;
+                    
+                    //
+                    //     CHECK THE INPUT PARAMETERS FOR ERRORS.
+                    //
+                    if( ((((((n<=0 || (double)(stp)<=(double)(0)) || (double)(ftol)<(double)(0)) || (double)(gtol)<(double)(zero)) || (double)(xtol)<(double)(zero)) || (double)(stpmin)<(double)(zero)) || (double)(stpmax)<(double)(stpmin)) || maxfev<=0 )
+                    {
+                        stage = 0;
+                        return;
+                    }
+                    
+                    //
+                    //     COMPUTE THE INITIAL GRADIENT IN THE SEARCH DIRECTION
+                    //     AND CHECK THAT S IS A DESCENT DIRECTION.
+                    //
+                    v = 0.0;
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        v += g[i_]*s[i_];
+                    }
+                    state.dginit = v;
+                    if( (double)(state.dginit)>=(double)(0) )
+                    {
+                        stage = 0;
+                        return;
+                    }
+                    
+                    //
+                    //     INITIALIZE LOCAL VARIABLES.
+                    //
+                    state.brackt = false;
+                    state.stage1 = true;
+                    nfev = 0;
+                    state.finit = f;
+                    state.dgtest = ftol*state.dginit;
+                    state.width = stpmax-stpmin;
+                    state.width1 = state.width/p5;
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        wa[i_] = x[i_];
+                    }
+                    
+                    //
+                    //     THE VARIABLES STX, FX, DGX CONTAIN THE VALUES OF THE STEP,
+                    //     FUNCTION, AND DIRECTIONAL DERIVATIVE AT THE BEST STEP.
+                    //     THE VARIABLES STY, FY, DGY CONTAIN THE VALUE OF THE STEP,
+                    //     FUNCTION, AND DERIVATIVE AT THE OTHER ENDPOINT OF
+                    //     THE INTERVAL OF UNCERTAINTY.
+                    //     THE VARIABLES STP, F, DG CONTAIN THE VALUES OF THE STEP,
+                    //     FUNCTION, AND DERIVATIVE AT THE CURRENT STEP.
+                    //
+                    state.stx = 0;
+                    state.fx = state.finit;
+                    state.dgx = state.dginit;
+                    state.sty = 0;
+                    state.fy = state.finit;
+                    state.dgy = state.dginit;
+                    
+                    //
+                    // NEXT
+                    //
+                    stage = 3;
+                    continue;
+                }
+                if( stage==3 )
+                {
+                    
+                    //
+                    //     START OF ITERATION.
+                    //
+                    //     SET THE MINIMUM AND MAXIMUM STEPS TO CORRESPOND
+                    //     TO THE PRESENT INTERVAL OF UNCERTAINTY.
+                    //
+                    if( state.brackt )
+                    {
+                        if( (double)(state.stx)<(double)(state.sty) )
+                        {
+                            state.stmin = state.stx;
+                            state.stmax = state.sty;
+                        }
+                        else
+                        {
+                            state.stmin = state.sty;
+                            state.stmax = state.stx;
+                        }
+                    }
+                    else
+                    {
+                        state.stmin = state.stx;
+                        state.stmax = stp+state.xtrapf*(stp-state.stx);
+                    }
+                    
+                    //
+                    //        FORCE THE STEP TO BE WITHIN THE BOUNDS STPMAX AND STPMIN.
+                    //
+                    if( (double)(stp)>(double)(stpmax) )
+                    {
+                        stp = stpmax;
+                    }
+                    if( (double)(stp)<(double)(stpmin) )
+                    {
+                        stp = stpmin;
+                    }
+                    
+                    //
+                    //        IF AN UNUSUAL TERMINATION IS TO OCCUR THEN LET
+                    //        STP BE THE LOWEST POINT OBTAINED SO FAR.
+                    //
+                    if( (((state.brackt && ((double)(stp)<=(double)(state.stmin) || (double)(stp)>=(double)(state.stmax))) || nfev>=maxfev-1) || state.infoc==0) || (state.brackt && (double)(state.stmax-state.stmin)<=(double)(xtol*state.stmax)) )
+                    {
+                        stp = state.stx;
+                    }
+                    
+                    //
+                    //        EVALUATE THE FUNCTION AND GRADIENT AT STP
+                    //        AND COMPUTE THE DIRECTIONAL DERIVATIVE.
+                    //
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        x[i_] = wa[i_];
+                    }
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        x[i_] = x[i_] + stp*s[i_];
+                    }
+                    
+                    //
+                    // NEXT
+                    //
+                    stage = 4;
+                    return;
+                }
+                if( stage==4 )
+                {
+                    info = 0;
+                    nfev = nfev+1;
+                    v = 0.0;
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        v += g[i_]*s[i_];
+                    }
+                    state.dg = v;
+                    state.ftest1 = state.finit+stp*state.dgtest;
+                    
+                    //
+                    //        TEST FOR CONVERGENCE.
+                    //
+                    if( (state.brackt && ((double)(stp)<=(double)(state.stmin) || (double)(stp)>=(double)(state.stmax))) || state.infoc==0 )
+                    {
+                        info = 6;
+                    }
+                    if( ((double)(stp)==(double)(stpmax) && (double)(f)<=(double)(state.ftest1)) && (double)(state.dg)<=(double)(state.dgtest) )
+                    {
+                        info = 5;
+                    }
+                    if( (double)(stp)==(double)(stpmin) && ((double)(f)>(double)(state.ftest1) || (double)(state.dg)>=(double)(state.dgtest)) )
+                    {
+                        info = 4;
+                    }
+                    if( nfev>=maxfev )
+                    {
+                        info = 3;
+                    }
+                    if( state.brackt && (double)(state.stmax-state.stmin)<=(double)(xtol*state.stmax) )
+                    {
+                        info = 2;
+                    }
+                    if( (double)(f)<=(double)(state.ftest1) && (double)(Math.Abs(state.dg))<=(double)(-(gtol*state.dginit)) )
+                    {
+                        info = 1;
+                    }
+                    
+                    //
+                    //        CHECK FOR TERMINATION.
+                    //
+                    if( info!=0 )
+                    {
+                        stage = 0;
+                        return;
+                    }
+                    
+                    //
+                    //        IN THE FIRST STAGE WE SEEK A STEP FOR WHICH THE MODIFIED
+                    //        FUNCTION HAS A NONPOSITIVE VALUE AND NONNEGATIVE DERIVATIVE.
+                    //
+                    if( (state.stage1 && (double)(f)<=(double)(state.ftest1)) && (double)(state.dg)>=(double)(Math.Min(ftol, gtol)*state.dginit) )
+                    {
+                        state.stage1 = false;
+                    }
+                    
+                    //
+                    //        A MODIFIED FUNCTION IS USED TO PREDICT THE STEP ONLY IF
+                    //        WE HAVE NOT OBTAINED A STEP FOR WHICH THE MODIFIED
+                    //        FUNCTION HAS A NONPOSITIVE FUNCTION VALUE AND NONNEGATIVE
+                    //        DERIVATIVE, AND IF A LOWER FUNCTION VALUE HAS BEEN
+                    //        OBTAINED BUT THE DECREASE IS NOT SUFFICIENT.
+                    //
+                    if( (state.stage1 && (double)(f)<=(double)(state.fx)) && (double)(f)>(double)(state.ftest1) )
+                    {
+                        
+                        //
+                        //           DEFINE THE MODIFIED FUNCTION AND DERIVATIVE VALUES.
+                        //
+                        state.fm = f-stp*state.dgtest;
+                        state.fxm = state.fx-state.stx*state.dgtest;
+                        state.fym = state.fy-state.sty*state.dgtest;
+                        state.dgm = state.dg-state.dgtest;
+                        state.dgxm = state.dgx-state.dgtest;
+                        state.dgym = state.dgy-state.dgtest;
+                        
+                        //
+                        //           CALL CSTEP TO UPDATE THE INTERVAL OF UNCERTAINTY
+                        //           AND TO COMPUTE THE NEW STEP.
+                        //
+                        mnlmcstep(ref state.stx, ref state.fxm, ref state.dgxm, ref state.sty, ref state.fym, ref state.dgym, ref stp, state.fm, state.dgm, ref state.brackt, state.stmin, state.stmax, ref state.infoc);
+                        
+                        //
+                        //           RESET THE FUNCTION AND GRADIENT VALUES FOR F.
+                        //
+                        state.fx = state.fxm+state.stx*state.dgtest;
+                        state.fy = state.fym+state.sty*state.dgtest;
+                        state.dgx = state.dgxm+state.dgtest;
+                        state.dgy = state.dgym+state.dgtest;
+                    }
+                    else
+                    {
+                        
+                        //
+                        //           CALL MCSTEP TO UPDATE THE INTERVAL OF UNCERTAINTY
+                        //           AND TO COMPUTE THE NEW STEP.
+                        //
+                        mnlmcstep(ref state.stx, ref state.fx, ref state.dgx, ref state.sty, ref state.fy, ref state.dgy, ref stp, f, state.dg, ref state.brackt, state.stmin, state.stmax, ref state.infoc);
+                    }
+                    
+                    //
+                    //        FORCE A SUFFICIENT DECREASE IN THE SIZE OF THE
+                    //        INTERVAL OF UNCERTAINTY.
+                    //
+                    if( state.brackt )
+                    {
+                        if( (double)(Math.Abs(state.sty-state.stx))>=(double)(p66*state.width1) )
+                        {
+                            stp = state.stx+p5*(state.sty-state.stx);
+                        }
+                        state.width1 = state.width;
+                        state.width = Math.Abs(state.sty-state.stx);
+                    }
+                    
+                    //
+                    //  NEXT.
+                    //
+                    stage = 3;
+                    continue;
+                }
+            }
+        }
+
+
+        private static void mnlmcstep(ref double stx,
+            ref double fx,
+            ref double dx,
+            ref double sty,
+            ref double fy,
+            ref double dy,
+            ref double stp,
+            double fp,
+            double dp,
+            ref bool brackt,
+            double stmin,
+            double stmax,
+            ref int info)
+        {
+            bool bound = new bool();
+            double gamma = 0;
+            double p = 0;
+            double q = 0;
+            double r = 0;
+            double s = 0;
+            double sgnd = 0;
+            double stpc = 0;
+            double stpf = 0;
+            double stpq = 0;
+            double theta = 0;
+
+            info = 0;
+            
+            //
+            //     CHECK THE INPUT PARAMETERS FOR ERRORS.
+            //
+            if( ((brackt && ((double)(stp)<=(double)(Math.Min(stx, sty)) || (double)(stp)>=(double)(Math.Max(stx, sty)))) || (double)(dx*(stp-stx))>=(double)(0)) || (double)(stmax)<(double)(stmin) )
+            {
+                return;
+            }
+            
+            //
+            //     DETERMINE IF THE DERIVATIVES HAVE OPPOSITE SIGN.
+            //
+            sgnd = dp*(dx/Math.Abs(dx));
+            
+            //
+            //     FIRST CASE. A HIGHER FUNCTION VALUE.
+            //     THE MINIMUM IS BRACKETED. IF THE CUBIC STEP IS CLOSER
+            //     TO STX THAN THE QUADRATIC STEP, THE CUBIC STEP IS TAKEN,
+            //     ELSE THE AVERAGE OF THE CUBIC AND QUADRATIC STEPS IS TAKEN.
+            //
+            if( (double)(fp)>(double)(fx) )
+            {
+                info = 1;
+                bound = true;
+                theta = 3*(fx-fp)/(stp-stx)+dx+dp;
+                s = Math.Max(Math.Abs(theta), Math.Max(Math.Abs(dx), Math.Abs(dp)));
+                gamma = s*Math.Sqrt(math.sqr(theta/s)-dx/s*(dp/s));
+                if( (double)(stp)<(double)(stx) )
+                {
+                    gamma = -gamma;
+                }
+                p = gamma-dx+theta;
+                q = gamma-dx+gamma+dp;
+                r = p/q;
+                stpc = stx+r*(stp-stx);
+                stpq = stx+dx/((fx-fp)/(stp-stx)+dx)/2*(stp-stx);
+                if( (double)(Math.Abs(stpc-stx))<(double)(Math.Abs(stpq-stx)) )
+                {
+                    stpf = stpc;
+                }
+                else
+                {
+                    stpf = stpc+(stpq-stpc)/2;
+                }
+                brackt = true;
+            }
+            else
+            {
+                if( (double)(sgnd)<(double)(0) )
+                {
+                    
+                    //
+                    //     SECOND CASE. A LOWER FUNCTION VALUE AND DERIVATIVES OF
+                    //     OPPOSITE SIGN. THE MINIMUM IS BRACKETED. IF THE CUBIC
+                    //     STEP IS CLOSER TO STX THAN THE QUADRATIC (SECANT) STEP,
+                    //     THE CUBIC STEP IS TAKEN, ELSE THE QUADRATIC STEP IS TAKEN.
+                    //
+                    info = 2;
+                    bound = false;
+                    theta = 3*(fx-fp)/(stp-stx)+dx+dp;
+                    s = Math.Max(Math.Abs(theta), Math.Max(Math.Abs(dx), Math.Abs(dp)));
+                    gamma = s*Math.Sqrt(math.sqr(theta/s)-dx/s*(dp/s));
+                    if( (double)(stp)>(double)(stx) )
+                    {
+                        gamma = -gamma;
+                    }
+                    p = gamma-dp+theta;
+                    q = gamma-dp+gamma+dx;
+                    r = p/q;
+                    stpc = stp+r*(stx-stp);
+                    stpq = stp+dp/(dp-dx)*(stx-stp);
+                    if( (double)(Math.Abs(stpc-stp))>(double)(Math.Abs(stpq-stp)) )
+                    {
+                        stpf = stpc;
+                    }
+                    else
+                    {
+                        stpf = stpq;
+                    }
+                    brackt = true;
+                }
+                else
+                {
+                    if( (double)(Math.Abs(dp))<(double)(Math.Abs(dx)) )
+                    {
+                        
+                        //
+                        //     THIRD CASE. A LOWER FUNCTION VALUE, DERIVATIVES OF THE
+                        //     SAME SIGN, AND THE MAGNITUDE OF THE DERIVATIVE DECREASES.
+                        //     THE CUBIC STEP IS ONLY USED IF THE CUBIC TENDS TO INFINITY
+                        //     IN THE DIRECTION OF THE STEP OR IF THE MINIMUM OF THE CUBIC
+                        //     IS BEYOND STP. OTHERWISE THE CUBIC STEP IS DEFINED TO BE
+                        //     EITHER STPMIN OR STPMAX. THE QUADRATIC (SECANT) STEP IS ALSO
+                        //     COMPUTED AND IF THE MINIMUM IS BRACKETED THEN THE THE STEP
+                        //     CLOSEST TO STX IS TAKEN, ELSE THE STEP FARTHEST AWAY IS TAKEN.
+                        //
+                        info = 3;
+                        bound = true;
+                        theta = 3*(fx-fp)/(stp-stx)+dx+dp;
+                        s = Math.Max(Math.Abs(theta), Math.Max(Math.Abs(dx), Math.Abs(dp)));
+                        
+                        //
+                        //        THE CASE GAMMA = 0 ONLY ARISES IF THE CUBIC DOES NOT TEND
+                        //        TO INFINITY IN THE DIRECTION OF THE STEP.
+                        //
+                        gamma = s*Math.Sqrt(Math.Max(0, math.sqr(theta/s)-dx/s*(dp/s)));
+                        if( (double)(stp)>(double)(stx) )
+                        {
+                            gamma = -gamma;
+                        }
+                        p = gamma-dp+theta;
+                        q = gamma+(dx-dp)+gamma;
+                        r = p/q;
+                        if( (double)(r)<(double)(0) && (double)(gamma)!=(double)(0) )
+                        {
+                            stpc = stp+r*(stx-stp);
+                        }
+                        else
+                        {
+                            if( (double)(stp)>(double)(stx) )
+                            {
+                                stpc = stmax;
+                            }
+                            else
+                            {
+                                stpc = stmin;
+                            }
+                        }
+                        stpq = stp+dp/(dp-dx)*(stx-stp);
+                        if( brackt )
+                        {
+                            if( (double)(Math.Abs(stp-stpc))<(double)(Math.Abs(stp-stpq)) )
+                            {
+                                stpf = stpc;
+                            }
+                            else
+                            {
+                                stpf = stpq;
+                            }
+                        }
+                        else
+                        {
+                            if( (double)(Math.Abs(stp-stpc))>(double)(Math.Abs(stp-stpq)) )
+                            {
+                                stpf = stpc;
+                            }
+                            else
+                            {
+                                stpf = stpq;
+                            }
+                        }
+                    }
+                    else
+                    {
+                        
+                        //
+                        //     FOURTH CASE. A LOWER FUNCTION VALUE, DERIVATIVES OF THE
+                        //     SAME SIGN, AND THE MAGNITUDE OF THE DERIVATIVE DOES
+                        //     NOT DECREASE. IF THE MINIMUM IS NOT BRACKETED, THE STEP
+                        //     IS EITHER STPMIN OR STPMAX, ELSE THE CUBIC STEP IS TAKEN.
+                        //
+                        info = 4;
+                        bound = false;
+                        if( brackt )
+                        {
+                            theta = 3*(fp-fy)/(sty-stp)+dy+dp;
+                            s = Math.Max(Math.Abs(theta), Math.Max(Math.Abs(dy), Math.Abs(dp)));
+                            gamma = s*Math.Sqrt(math.sqr(theta/s)-dy/s*(dp/s));
+                            if( (double)(stp)>(double)(sty) )
+                            {
+                                gamma = -gamma;
+                            }
+                            p = gamma-dp+theta;
+                            q = gamma-dp+gamma+dy;
+                            r = p/q;
+                            stpc = stp+r*(sty-stp);
+                            stpf = stpc;
+                        }
+                        else
+                        {
+                            if( (double)(stp)>(double)(stx) )
+                            {
+                                stpf = stmax;
+                            }
+                            else
+                            {
+                                stpf = stmin;
+                            }
+                        }
+                    }
+                }
+            }
+            
+            //
+            //     UPDATE THE INTERVAL OF UNCERTAINTY. THIS UPDATE DOES NOT
+            //     DEPEND ON THE NEW STEP OR THE CASE ANALYSIS ABOVE.
+            //
+            if( (double)(fp)>(double)(fx) )
+            {
+                sty = stp;
+                fy = fp;
+                dy = dp;
+            }
+            else
+            {
+                if( (double)(sgnd)<(double)(0.0) )
+                {
+                    sty = stx;
+                    fy = fx;
+                    dy = dx;
+                }
+                stx = stp;
+                fx = fp;
+                dx = dp;
+            }
+            
+            //
+            //     COMPUTE THE NEW STEP AND SAFEGUARD IT.
+            //
+            stpf = Math.Min(stmax, stpf);
+            stpf = Math.Max(stmin, stpf);
+            stp = stpf;
+            if( brackt && bound )
+            {
+                if( (double)(sty)>(double)(stx) )
+                {
+                    stp = Math.Min(stx+0.66*(sty-stx), stp);
+                }
+                else
+                {
+                    stp = Math.Max(stx+0.66*(sty-stx), stp);
+                }
+            }
+        }
+
+
+    }
+    public class mcpd
+    {
+        /*************************************************************************
+        This structure is a MCPD (Markov Chains for Population Data) solver.
+
+        You should use ALGLIB functions in order to work with this object.
+
+          -- ALGLIB --
+             Copyright 23.05.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public class mcpdstate
+        {
+            public int n;
+            public int[] states;
+            public int npairs;
+            public double[,] data;
+            public double[,] ec;
+            public double[,] bndl;
+            public double[,] bndu;
+            public double[,] c;
+            public int[] ct;
+            public int ccnt;
+            public double[] pw;
+            public double[,] priorp;
+            public double regterm;
+            public minbleic.minbleicstate bs;
+            public int repinneriterationscount;
+            public int repouteriterationscount;
+            public int repnfev;
+            public int repterminationtype;
+            public minbleic.minbleicreport br;
+            public double[] tmpp;
+            public double[] effectivew;
+            public double[] effectivebndl;
+            public double[] effectivebndu;
+            public double[,] effectivec;
+            public int[] effectivect;
+            public double[] h;
+            public double[,] p;
+            public mcpdstate()
+            {
+                states = new int[0];
+                data = new double[0,0];
+                ec = new double[0,0];
+                bndl = new double[0,0];
+                bndu = new double[0,0];
+                c = new double[0,0];
+                ct = new int[0];
+                pw = new double[0];
+                priorp = new double[0,0];
+                bs = new minbleic.minbleicstate();
+                br = new minbleic.minbleicreport();
+                tmpp = new double[0];
+                effectivew = new double[0];
+                effectivebndl = new double[0];
+                effectivebndu = new double[0];
+                effectivec = new double[0,0];
+                effectivect = new int[0];
+                h = new double[0];
+                p = new double[0,0];
+            }
+        };
+
+
+        /*************************************************************************
+        This structure is a MCPD training report:
+            InnerIterationsCount    -   number of inner iterations of the
+                                        underlying optimization algorithm
+            OuterIterationsCount    -   number of outer iterations of the
+                                        underlying optimization algorithm
+            NFEV                    -   number of merit function evaluations
+            TerminationType         -   termination type
+                                        (same as for MinBLEIC optimizer, positive
+                                        values denote success, negative ones -
+                                        failure)
+
+          -- ALGLIB --
+             Copyright 23.05.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public class mcpdreport
+        {
+            public int inneriterationscount;
+            public int outeriterationscount;
+            public int nfev;
+            public int terminationtype;
+        };
+
+
+
+
+        public const double xtol = 1.0E-8;
+
+
+        /*************************************************************************
+        DESCRIPTION:
+
+        This function creates MCPD (Markov Chains for Population Data) solver.
+
+        This  solver  can  be  used  to find transition matrix P for N-dimensional
+        prediction  problem  where transition from X[i] to X[i+1] is  modelled  as
+            X[i+1] = P*X[i]
+        where X[i] and X[i+1] are N-dimensional population vectors (components  of
+        each X are non-negative), and P is a N*N transition matrix (elements of  P
+        are non-negative, each column sums to 1.0).
+
+        Such models arise when when:
+        * there is some population of individuals
+        * individuals can have different states
+        * individuals can transit from one state to another
+        * population size is constant, i.e. there is no new individuals and no one
+          leaves population
+        * you want to model transitions of individuals from one state into another
+
+        USAGE:
+
+        Here we give very brief outline of the MCPD. We strongly recommend you  to
+        read examples in the ALGLIB Reference Manual and to read ALGLIB User Guide
+        on data analysis which is available at http://www.alglib.net/dataanalysis/
+
+        1. User initializes algorithm state with MCPDCreate() call
+
+        2. User  adds  one  or  more  tracks -  sequences of states which describe
+           evolution of a system being modelled from different starting conditions
+
+        3. User may add optional boundary, equality  and/or  linear constraints on
+           the coefficients of P by calling one of the following functions:
+           * MCPDSetEC() to set equality constraints
+           * MCPDSetBC() to set bound constraints
+           * MCPDSetLC() to set linear constraints
+
+        4. Optionally,  user  may  set  custom  weights  for prediction errors (by
+           default, algorithm assigns non-equal, automatically chosen weights  for
+           errors in the prediction of different components of X). It can be  done
+           with a call of MCPDSetPredictionWeights() function.
+
+        5. User calls MCPDSolve() function which takes algorithm  state and
+           pointer (delegate, etc.) to callback function which calculates F/G.
+
+        6. User calls MCPDResults() to get solution
+
+        INPUT PARAMETERS:
+            N       -   problem dimension, N>=1
+
+        OUTPUT PARAMETERS:
+            State   -   structure stores algorithm state
+
+          -- ALGLIB --
+             Copyright 23.05.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mcpdcreate(int n,
+            mcpdstate s)
+        {
+            alglib.ap.assert(n>=1, "MCPDCreate: N<1");
+            mcpdinit(n, -1, -1, s);
+        }
+
+
+        /*************************************************************************
+        DESCRIPTION:
+
+        This function is a specialized version of MCPDCreate()  function,  and  we
+        recommend  you  to read comments for this function for general information
+        about MCPD solver.
+
+        This  function  creates  MCPD (Markov Chains for Population  Data)  solver
+        for "Entry-state" model,  i.e. model  where transition from X[i] to X[i+1]
+        is modelled as
+            X[i+1] = P*X[i]
+        where
+            X[i] and X[i+1] are N-dimensional state vectors
+            P is a N*N transition matrix
+        and  one  selected component of X[] is called "entry" state and is treated
+        in a special way:
+            system state always transits from "entry" state to some another state
+            system state can not transit from any state into "entry" state
+        Such conditions basically mean that row of P which corresponds to  "entry"
+        state is zero.
+
+        Such models arise when:
+        * there is some population of individuals
+        * individuals can have different states
+        * individuals can transit from one state to another
+        * population size is NOT constant -  at every moment of time there is some
+          (unpredictable) amount of "new" individuals, which can transit into  one
+          of the states at the next turn, but still no one leaves population
+        * you want to model transitions of individuals from one state into another
+        * but you do NOT want to predict amount of "new"  individuals  because  it
+          does not depends on individuals already present (hence  system  can  not
+          transit INTO entry state - it can only transit FROM it).
+
+        This model is discussed  in  more  details  in  the ALGLIB User Guide (see
+        http://www.alglib.net/dataanalysis/ for more data).
+
+        INPUT PARAMETERS:
+            N       -   problem dimension, N>=2
+            EntryState- index of entry state, in 0..N-1
+
+        OUTPUT PARAMETERS:
+            State   -   structure stores algorithm state
+
+          -- ALGLIB --
+             Copyright 23.05.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mcpdcreateentry(int n,
+            int entrystate,
+            mcpdstate s)
+        {
+            alglib.ap.assert(n>=2, "MCPDCreateEntry: N<2");
+            alglib.ap.assert(entrystate>=0, "MCPDCreateEntry: EntryState<0");
+            alglib.ap.assert(entrystate<n, "MCPDCreateEntry: EntryState>=N");
+            mcpdinit(n, entrystate, -1, s);
+        }
+
+
+        /*************************************************************************
+        DESCRIPTION:
+
+        This function is a specialized version of MCPDCreate()  function,  and  we
+        recommend  you  to read comments for this function for general information
+        about MCPD solver.
+
+        This  function  creates  MCPD (Markov Chains for Population  Data)  solver
+        for "Exit-state" model,  i.e. model  where  transition from X[i] to X[i+1]
+        is modelled as
+            X[i+1] = P*X[i]
+        where
+            X[i] and X[i+1] are N-dimensional state vectors
+            P is a N*N transition matrix
+        and  one  selected component of X[] is called "exit"  state and is treated
+        in a special way:
+            system state can transit from any state into "exit" state
+            system state can not transit from "exit" state into any other state
+            transition operator discards "exit" state (makes it zero at each turn)
+        Such  conditions  basically  mean  that  column  of P which corresponds to
+        "exit" state is zero. Multiplication by such P may decrease sum of  vector
+        components.
+
+        Such models arise when:
+        * there is some population of individuals
+        * individuals can have different states
+        * individuals can transit from one state to another
+        * population size is NOT constant - individuals can move into "exit" state
+          and leave population at the next turn, but there are no new individuals
+        * amount of individuals which leave population can be predicted
+        * you want to model transitions of individuals from one state into another
+          (including transitions into the "exit" state)
+
+        This model is discussed  in  more  details  in  the ALGLIB User Guide (see
+        http://www.alglib.net/dataanalysis/ for more data).
+
+        INPUT PARAMETERS:
+            N       -   problem dimension, N>=2
+            ExitState-  index of exit state, in 0..N-1
+
+        OUTPUT PARAMETERS:
+            State   -   structure stores algorithm state
+
+          -- ALGLIB --
+             Copyright 23.05.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mcpdcreateexit(int n,
+            int exitstate,
+            mcpdstate s)
+        {
+            alglib.ap.assert(n>=2, "MCPDCreateExit: N<2");
+            alglib.ap.assert(exitstate>=0, "MCPDCreateExit: ExitState<0");
+            alglib.ap.assert(exitstate<n, "MCPDCreateExit: ExitState>=N");
+            mcpdinit(n, -1, exitstate, s);
+        }
+
+
+        /*************************************************************************
+        DESCRIPTION:
+
+        This function is a specialized version of MCPDCreate()  function,  and  we
+        recommend  you  to read comments for this function for general information
+        about MCPD solver.
+
+        This  function  creates  MCPD (Markov Chains for Population  Data)  solver
+        for "Entry-Exit-states" model, i.e. model where  transition  from  X[i] to
+        X[i+1] is modelled as
+            X[i+1] = P*X[i]
+        where
+            X[i] and X[i+1] are N-dimensional state vectors
+            P is a N*N transition matrix
+        one selected component of X[] is called "entry" state and is treated in  a
+        special way:
+            system state always transits from "entry" state to some another state
+            system state can not transit from any state into "entry" state
+        and another one component of X[] is called "exit" state and is treated  in
+        a special way too:
+            system state can transit from any state into "exit" state
+            system state can not transit from "exit" state into any other state
+            transition operator discards "exit" state (makes it zero at each turn)
+        Such conditions basically mean that:
+            row of P which corresponds to "entry" state is zero
+            column of P which corresponds to "exit" state is zero
+        Multiplication by such P may decrease sum of vector components.
+
+        Such models arise when:
+        * there is some population of individuals
+        * individuals can have different states
+        * individuals can transit from one state to another
+        * population size is NOT constant
+        * at every moment of time there is some (unpredictable)  amount  of  "new"
+          individuals, which can transit into one of the states at the next turn
+        * some  individuals  can  move  (predictably)  into "exit" state and leave
+          population at the next turn
+        * you want to model transitions of individuals from one state into another,
+          including transitions from the "entry" state and into the "exit" state.
+        * but you do NOT want to predict amount of "new"  individuals  because  it
+          does not depends on individuals already present (hence  system  can  not
+          transit INTO entry state - it can only transit FROM it).
+
+        This model is discussed  in  more  details  in  the ALGLIB User Guide (see
+        http://www.alglib.net/dataanalysis/ for more data).
+
+        INPUT PARAMETERS:
+            N       -   problem dimension, N>=2
+            EntryState- index of entry state, in 0..N-1
+            ExitState-  index of exit state, in 0..N-1
+
+        OUTPUT PARAMETERS:
+            State   -   structure stores algorithm state
+
+          -- ALGLIB --
+             Copyright 23.05.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mcpdcreateentryexit(int n,
+            int entrystate,
+            int exitstate,
+            mcpdstate s)
+        {
+            alglib.ap.assert(n>=2, "MCPDCreateEntryExit: N<2");
+            alglib.ap.assert(entrystate>=0, "MCPDCreateEntryExit: EntryState<0");
+            alglib.ap.assert(entrystate<n, "MCPDCreateEntryExit: EntryState>=N");
+            alglib.ap.assert(exitstate>=0, "MCPDCreateEntryExit: ExitState<0");
+            alglib.ap.assert(exitstate<n, "MCPDCreateEntryExit: ExitState>=N");
+            alglib.ap.assert(entrystate!=exitstate, "MCPDCreateEntryExit: EntryState=ExitState");
+            mcpdinit(n, entrystate, exitstate, s);
+        }
+
+
+        /*************************************************************************
+        This  function  is  used to add a track - sequence of system states at the
+        different moments of its evolution.
+
+        You  may  add  one  or several tracks to the MCPD solver. In case you have
+        several tracks, they won't overwrite each other. For example,  if you pass
+        two tracks, A1-A2-A3 (system at t=A+1, t=A+2 and t=A+3) and B1-B2-B3, then
+        solver will try to model transitions from t=A+1 to t=A+2, t=A+2 to  t=A+3,
+        t=B+1 to t=B+2, t=B+2 to t=B+3. But it WONT mix these two tracks - i.e. it
+        wont try to model transition from t=A+3 to t=B+1.
+
+        INPUT PARAMETERS:
+            S       -   solver
+            XY      -   track, array[K,N]:
+                        * I-th row is a state at t=I
+                        * elements of XY must be non-negative (exception will be
+                          thrown on negative elements)
+            K       -   number of points in a track
+                        * if given, only leading K rows of XY are used
+                        * if not given, automatically determined from size of XY
+
+        NOTES:
+
+        1. Track may contain either proportional or population data:
+           * with proportional data all rows of XY must sum to 1.0, i.e. we have
+             proportions instead of absolute population values
+           * with population data rows of XY contain population counts and generally
+             do not sum to 1.0 (although they still must be non-negative)
+
+          -- ALGLIB --
+             Copyright 23.05.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mcpdaddtrack(mcpdstate s,
+            double[,] xy,
+            int k)
+        {
+            int i = 0;
+            int j = 0;
+            int n = 0;
+            double s0 = 0;
+            double s1 = 0;
+
+            n = s.n;
+            alglib.ap.assert(k>=0, "MCPDAddTrack: K<0");
+            alglib.ap.assert(alglib.ap.cols(xy)>=n, "MCPDAddTrack: Cols(XY)<N");
+            alglib.ap.assert(alglib.ap.rows(xy)>=k, "MCPDAddTrack: Rows(XY)<K");
+            alglib.ap.assert(apserv.apservisfinitematrix(xy, k, n), "MCPDAddTrack: XY contains infinite or NaN elements");
+            for(i=0; i<=k-1; i++)
+            {
+                for(j=0; j<=n-1; j++)
+                {
+                    alglib.ap.assert((double)(xy[i,j])>=(double)(0), "MCPDAddTrack: XY contains negative elements");
+                }
+            }
+            if( k<2 )
+            {
+                return;
+            }
+            if( alglib.ap.rows(s.data)<s.npairs+k-1 )
+            {
+                apserv.rmatrixresize(ref s.data, Math.Max(2*alglib.ap.rows(s.data), s.npairs+k-1), 2*n);
+            }
+            for(i=0; i<=k-2; i++)
+            {
+                s0 = 0;
+                s1 = 0;
+                for(j=0; j<=n-1; j++)
+                {
+                    if( s.states[j]>=0 )
+                    {
+                        s0 = s0+xy[i,j];
+                    }
+                    if( s.states[j]<=0 )
+                    {
+                        s1 = s1+xy[i+1,j];
+                    }
+                }
+                if( (double)(s0)>(double)(0) && (double)(s1)>(double)(0) )
+                {
+                    for(j=0; j<=n-1; j++)
+                    {
+                        if( s.states[j]>=0 )
+                        {
+                            s.data[s.npairs,j] = xy[i,j]/s0;
+                        }
+                        else
+                        {
+                            s.data[s.npairs,j] = 0.0;
+                        }
+                        if( s.states[j]<=0 )
+                        {
+                            s.data[s.npairs,n+j] = xy[i+1,j]/s1;
+                        }
+                        else
+                        {
+                            s.data[s.npairs,n+j] = 0.0;
+                        }
+                    }
+                    s.npairs = s.npairs+1;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This function is used to add equality constraints on the elements  of  the
+        transition matrix P.
+
+        MCPD solver has four types of constraints which can be placed on P:
+        * user-specified equality constraints (optional)
+        * user-specified bound constraints (optional)
+        * user-specified general linear constraints (optional)
+        * basic constraints (always present):
+          * non-negativity: P[i,j]>=0
+          * consistency: every column of P sums to 1.0
+
+        Final  constraints  which  are  passed  to  the  underlying  optimizer are
+        calculated  as  intersection  of all present constraints. For example, you
+        may specify boundary constraint on P[0,0] and equality one:
+            0.1<=P[0,0]<=0.9
+            P[0,0]=0.5
+        Such  combination  of  constraints  will  be  silently  reduced  to  their
+        intersection, which is P[0,0]=0.5.
+
+        This  function  can  be  used  to  place equality constraints on arbitrary
+        subset of elements of P. Set of constraints is specified by EC, which  may
+        contain either NAN's or finite numbers from [0,1]. NAN denotes absence  of
+        constraint, finite number denotes equality constraint on specific  element
+        of P.
+
+        You can also  use  MCPDAddEC()  function  which  allows  to  ADD  equality
+        constraint  for  one  element  of P without changing constraints for other
+        elements.
+
+        These functions (MCPDSetEC and MCPDAddEC) interact as follows:
+        * there is internal matrix of equality constraints which is stored in  the
+          MCPD solver
+        * MCPDSetEC() replaces this matrix by another one (SET)
+        * MCPDAddEC() modifies one element of this matrix and  leaves  other  ones
+          unchanged (ADD)
+        * thus  MCPDAddEC()  call  preserves  all  modifications  done by previous
+          calls,  while  MCPDSetEC()  completely discards all changes  done to the
+          equality constraints.
+
+        INPUT PARAMETERS:
+            S       -   solver
+            EC      -   equality constraints, array[N,N]. Elements of  EC  can  be
+                        either NAN's or finite  numbers from  [0,1].  NAN  denotes
+                        absence  of  constraints,  while  finite  value    denotes
+                        equality constraint on the corresponding element of P.
+
+        NOTES:
+
+        1. infinite values of EC will lead to exception being thrown. Values  less
+        than 0.0 or greater than 1.0 will lead to error code being returned  after
+        call to MCPDSolve().
+
+          -- ALGLIB --
+             Copyright 23.05.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mcpdsetec(mcpdstate s,
+            double[,] ec)
+        {
+            int i = 0;
+            int j = 0;
+            int n = 0;
+
+            n = s.n;
+            alglib.ap.assert(alglib.ap.cols(ec)>=n, "MCPDSetEC: Cols(EC)<N");
+            alglib.ap.assert(alglib.ap.rows(ec)>=n, "MCPDSetEC: Rows(EC)<N");
+            for(i=0; i<=n-1; i++)
+            {
+                for(j=0; j<=n-1; j++)
+                {
+                    alglib.ap.assert(math.isfinite(ec[i,j]) || Double.IsNaN(ec[i,j]), "MCPDSetEC: EC containts infinite elements");
+                    s.ec[i,j] = ec[i,j];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This function is used to add equality constraints on the elements  of  the
+        transition matrix P.
+
+        MCPD solver has four types of constraints which can be placed on P:
+        * user-specified equality constraints (optional)
+        * user-specified bound constraints (optional)
+        * user-specified general linear constraints (optional)
+        * basic constraints (always present):
+          * non-negativity: P[i,j]>=0
+          * consistency: every column of P sums to 1.0
+
+        Final  constraints  which  are  passed  to  the  underlying  optimizer are
+        calculated  as  intersection  of all present constraints. For example, you
+        may specify boundary constraint on P[0,0] and equality one:
+            0.1<=P[0,0]<=0.9
+            P[0,0]=0.5
+        Such  combination  of  constraints  will  be  silently  reduced  to  their
+        intersection, which is P[0,0]=0.5.
+
+        This function can be used to ADD equality constraint for one element of  P
+        without changing constraints for other elements.
+
+        You  can  also  use  MCPDSetEC()  function  which  allows  you  to specify
+        arbitrary set of equality constraints in one call.
+
+        These functions (MCPDSetEC and MCPDAddEC) interact as follows:
+        * there is internal matrix of equality constraints which is stored in the
+          MCPD solver
+        * MCPDSetEC() replaces this matrix by another one (SET)
+        * MCPDAddEC() modifies one element of this matrix and leaves  other  ones
+          unchanged (ADD)
+        * thus  MCPDAddEC()  call  preserves  all  modifications done by previous
+          calls,  while  MCPDSetEC()  completely discards all changes done to the
+          equality constraints.
+
+        INPUT PARAMETERS:
+            S       -   solver
+            I       -   row index of element being constrained
+            J       -   column index of element being constrained
+            C       -   value (constraint for P[I,J]).  Can  be  either  NAN  (no
+                        constraint) or finite value from [0,1].
+                        
+        NOTES:
+
+        1. infinite values of C  will lead to exception being thrown. Values  less
+        than 0.0 or greater than 1.0 will lead to error code being returned  after
+        call to MCPDSolve().
+
+          -- ALGLIB --
+             Copyright 23.05.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mcpdaddec(mcpdstate s,
+            int i,
+            int j,
+            double c)
+        {
+            alglib.ap.assert(i>=0, "MCPDAddEC: I<0");
+            alglib.ap.assert(i<s.n, "MCPDAddEC: I>=N");
+            alglib.ap.assert(j>=0, "MCPDAddEC: J<0");
+            alglib.ap.assert(j<s.n, "MCPDAddEC: J>=N");
+            alglib.ap.assert(Double.IsNaN(c) || math.isfinite(c), "MCPDAddEC: C is not finite number or NAN");
+            s.ec[i,j] = c;
+        }
+
+
+        /*************************************************************************
+        This function is used to add bound constraints  on  the  elements  of  the
+        transition matrix P.
+
+        MCPD solver has four types of constraints which can be placed on P:
+        * user-specified equality constraints (optional)
+        * user-specified bound constraints (optional)
+        * user-specified general linear constraints (optional)
+        * basic constraints (always present):
+          * non-negativity: P[i,j]>=0
+          * consistency: every column of P sums to 1.0
+
+        Final  constraints  which  are  passed  to  the  underlying  optimizer are
+        calculated  as  intersection  of all present constraints. For example, you
+        may specify boundary constraint on P[0,0] and equality one:
+            0.1<=P[0,0]<=0.9
+            P[0,0]=0.5
+        Such  combination  of  constraints  will  be  silently  reduced  to  their
+        intersection, which is P[0,0]=0.5.
+
+        This  function  can  be  used  to  place bound   constraints  on arbitrary
+        subset  of  elements  of  P.  Set of constraints is specified by BndL/BndU
+        matrices, which may contain arbitrary combination  of  finite  numbers  or
+        infinities (like -INF<x<=0.5 or 0.1<=x<+INF).
+
+        You can also use MCPDAddBC() function which allows to ADD bound constraint
+        for one element of P without changing constraints for other elements.
+
+        These functions (MCPDSetBC and MCPDAddBC) interact as follows:
+        * there is internal matrix of bound constraints which is stored in the
+          MCPD solver
+        * MCPDSetBC() replaces this matrix by another one (SET)
+        * MCPDAddBC() modifies one element of this matrix and  leaves  other  ones
+          unchanged (ADD)
+        * thus  MCPDAddBC()  call  preserves  all  modifications  done by previous
+          calls,  while  MCPDSetBC()  completely discards all changes  done to the
+          equality constraints.
+
+        INPUT PARAMETERS:
+            S       -   solver
+            BndL    -   lower bounds constraints, array[N,N]. Elements of BndL can
+                        be finite numbers or -INF.
+            BndU    -   upper bounds constraints, array[N,N]. Elements of BndU can
+                        be finite numbers or +INF.
+
+          -- ALGLIB --
+             Copyright 23.05.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mcpdsetbc(mcpdstate s,
+            double[,] bndl,
+            double[,] bndu)
+        {
+            int i = 0;
+            int j = 0;
+            int n = 0;
+
+            n = s.n;
+            alglib.ap.assert(alglib.ap.cols(bndl)>=n, "MCPDSetBC: Cols(BndL)<N");
+            alglib.ap.assert(alglib.ap.rows(bndl)>=n, "MCPDSetBC: Rows(BndL)<N");
+            alglib.ap.assert(alglib.ap.cols(bndu)>=n, "MCPDSetBC: Cols(BndU)<N");
+            alglib.ap.assert(alglib.ap.rows(bndu)>=n, "MCPDSetBC: Rows(BndU)<N");
+            for(i=0; i<=n-1; i++)
+            {
+                for(j=0; j<=n-1; j++)
+                {
+                    alglib.ap.assert(math.isfinite(bndl[i,j]) || Double.IsNegativeInfinity(bndl[i,j]), "MCPDSetBC: BndL containts NAN or +INF");
+                    alglib.ap.assert(math.isfinite(bndu[i,j]) || Double.IsPositiveInfinity(bndu[i,j]), "MCPDSetBC: BndU containts NAN or -INF");
+                    s.bndl[i,j] = bndl[i,j];
+                    s.bndu[i,j] = bndu[i,j];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This function is used to add bound constraints  on  the  elements  of  the
+        transition matrix P.
+
+        MCPD solver has four types of constraints which can be placed on P:
+        * user-specified equality constraints (optional)
+        * user-specified bound constraints (optional)
+        * user-specified general linear constraints (optional)
+        * basic constraints (always present):
+          * non-negativity: P[i,j]>=0
+          * consistency: every column of P sums to 1.0
+
+        Final  constraints  which  are  passed  to  the  underlying  optimizer are
+        calculated  as  intersection  of all present constraints. For example, you
+        may specify boundary constraint on P[0,0] and equality one:
+            0.1<=P[0,0]<=0.9
+            P[0,0]=0.5
+        Such  combination  of  constraints  will  be  silently  reduced  to  their
+        intersection, which is P[0,0]=0.5.
+
+        This  function  can  be  used to ADD bound constraint for one element of P
+        without changing constraints for other elements.
+
+        You  can  also  use  MCPDSetBC()  function  which  allows to  place  bound
+        constraints  on arbitrary subset of elements of P.   Set of constraints is
+        specified  by  BndL/BndU matrices, which may contain arbitrary combination
+        of finite numbers or infinities (like -INF<x<=0.5 or 0.1<=x<+INF).
+
+        These functions (MCPDSetBC and MCPDAddBC) interact as follows:
+        * there is internal matrix of bound constraints which is stored in the
+          MCPD solver
+        * MCPDSetBC() replaces this matrix by another one (SET)
+        * MCPDAddBC() modifies one element of this matrix and  leaves  other  ones
+          unchanged (ADD)
+        * thus  MCPDAddBC()  call  preserves  all  modifications  done by previous
+          calls,  while  MCPDSetBC()  completely discards all changes  done to the
+          equality constraints.
+
+        INPUT PARAMETERS:
+            S       -   solver
+            I       -   row index of element being constrained
+            J       -   column index of element being constrained
+            BndL    -   lower bound
+            BndU    -   upper bound
+
+          -- ALGLIB --
+             Copyright 23.05.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mcpdaddbc(mcpdstate s,
+            int i,
+            int j,
+            double bndl,
+            double bndu)
+        {
+            alglib.ap.assert(i>=0, "MCPDAddBC: I<0");
+            alglib.ap.assert(i<s.n, "MCPDAddBC: I>=N");
+            alglib.ap.assert(j>=0, "MCPDAddBC: J<0");
+            alglib.ap.assert(j<s.n, "MCPDAddBC: J>=N");
+            alglib.ap.assert(math.isfinite(bndl) || Double.IsNegativeInfinity(bndl), "MCPDAddBC: BndL is NAN or +INF");
+            alglib.ap.assert(math.isfinite(bndu) || Double.IsPositiveInfinity(bndu), "MCPDAddBC: BndU is NAN or -INF");
+            s.bndl[i,j] = bndl;
+            s.bndu[i,j] = bndu;
+        }
+
+
+        /*************************************************************************
+        This function is used to set linear equality/inequality constraints on the
+        elements of the transition matrix P.
+
+        This function can be used to set one or several general linear constraints
+        on the elements of P. Two types of constraints are supported:
+        * equality constraints
+        * inequality constraints (both less-or-equal and greater-or-equal)
+
+        Coefficients  of  constraints  are  specified  by  matrix  C (one  of  the
+        parameters).  One  row  of  C  corresponds  to  one  constraint.   Because
+        transition  matrix P has N*N elements,  we  need  N*N columns to store all
+        coefficients  (they  are  stored row by row), and one more column to store
+        right part - hence C has N*N+1 columns.  Constraint  kind is stored in the
+        CT array.
+
+        Thus, I-th linear constraint is
+            P[0,0]*C[I,0] + P[0,1]*C[I,1] + .. + P[0,N-1]*C[I,N-1] +
+                + P[1,0]*C[I,N] + P[1,1]*C[I,N+1] + ... +
+                + P[N-1,N-1]*C[I,N*N-1]  ?=?  C[I,N*N]
+        where ?=? can be either "=" (CT[i]=0), "<=" (CT[i]<0) or ">=" (CT[i]>0).
+
+        Your constraint may involve only some subset of P (less than N*N elements).
+        For example it can be something like
+            P[0,0] + P[0,1] = 0.5
+        In this case you still should pass matrix  with N*N+1 columns, but all its
+        elements (except for C[0,0], C[0,1] and C[0,N*N-1]) will be zero.
+
+        INPUT PARAMETERS:
+            S       -   solver
+            C       -   array[K,N*N+1] - coefficients of constraints
+                        (see above for complete description)
+            CT      -   array[K] - constraint types
+                        (see above for complete description)
+            K       -   number of equality/inequality constraints, K>=0:
+                        * if given, only leading K elements of C/CT are used
+                        * if not given, automatically determined from sizes of C/CT
+
+          -- ALGLIB --
+             Copyright 23.05.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mcpdsetlc(mcpdstate s,
+            double[,] c,
+            int[] ct,
+            int k)
+        {
+            int i = 0;
+            int j = 0;
+            int n = 0;
+
+            n = s.n;
+            alglib.ap.assert(alglib.ap.cols(c)>=n*n+1, "MCPDSetLC: Cols(C)<N*N+1");
+            alglib.ap.assert(alglib.ap.rows(c)>=k, "MCPDSetLC: Rows(C)<K");
+            alglib.ap.assert(alglib.ap.len(ct)>=k, "MCPDSetLC: Len(CT)<K");
+            alglib.ap.assert(apserv.apservisfinitematrix(c, k, n*n+1), "MCPDSetLC: C contains infinite or NaN values!");
+            apserv.rmatrixsetlengthatleast(ref s.c, k, n*n+1);
+            apserv.ivectorsetlengthatleast(ref s.ct, k);
+            for(i=0; i<=k-1; i++)
+            {
+                for(j=0; j<=n*n; j++)
+                {
+                    s.c[i,j] = c[i,j];
+                }
+                s.ct[i] = ct[i];
+            }
+            s.ccnt = k;
+        }
+
+
+        /*************************************************************************
+        This function allows to  tune  amount  of  Tikhonov  regularization  being
+        applied to your problem.
+
+        By default, regularizing term is equal to r*||P-prior_P||^2, where r is  a
+        small non-zero value,  P is transition matrix, prior_P is identity matrix,
+        ||X||^2 is a sum of squared elements of X.
+
+        This  function  allows  you to change coefficient r. You can  also  change
+        prior values with MCPDSetPrior() function.
+
+        INPUT PARAMETERS:
+            S       -   solver
+            V       -   regularization  coefficient, finite non-negative value. It
+                        is  not  recommended  to specify zero value unless you are
+                        pretty sure that you want it.
+
+          -- ALGLIB --
+             Copyright 23.05.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mcpdsettikhonovregularizer(mcpdstate s,
+            double v)
+        {
+            alglib.ap.assert(math.isfinite(v), "MCPDSetTikhonovRegularizer: V is infinite or NAN");
+            alglib.ap.assert((double)(v)>=(double)(0.0), "MCPDSetTikhonovRegularizer: V is less than zero");
+            s.regterm = v;
+        }
+
+
+        /*************************************************************************
+        This  function  allows to set prior values used for regularization of your
+        problem.
+
+        By default, regularizing term is equal to r*||P-prior_P||^2, where r is  a
+        small non-zero value,  P is transition matrix, prior_P is identity matrix,
+        ||X||^2 is a sum of squared elements of X.
+
+        This  function  allows  you to change prior values prior_P. You  can  also
+        change r with MCPDSetTikhonovRegularizer() function.
+
+        INPUT PARAMETERS:
+            S       -   solver
+            PP      -   array[N,N], matrix of prior values:
+                        1. elements must be real numbers from [0,1]
+                        2. columns must sum to 1.0.
+                        First property is checked (exception is thrown otherwise),
+                        while second one is not checked/enforced.
+
+          -- ALGLIB --
+             Copyright 23.05.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mcpdsetprior(mcpdstate s,
+            double[,] pp)
+        {
+            int i = 0;
+            int j = 0;
+            int n = 0;
+
+            pp = (double[,])pp.Clone();
+
+            n = s.n;
+            alglib.ap.assert(alglib.ap.cols(pp)>=n, "MCPDSetPrior: Cols(PP)<N");
+            alglib.ap.assert(alglib.ap.rows(pp)>=n, "MCPDSetPrior: Rows(PP)<K");
+            for(i=0; i<=n-1; i++)
+            {
+                for(j=0; j<=n-1; j++)
+                {
+                    alglib.ap.assert(math.isfinite(pp[i,j]), "MCPDSetPrior: PP containts infinite elements");
+                    alglib.ap.assert((double)(pp[i,j])>=(double)(0.0) && (double)(pp[i,j])<=(double)(1.0), "MCPDSetPrior: PP[i,j] is less than 0.0 or greater than 1.0");
+                    s.priorp[i,j] = pp[i,j];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This function is used to change prediction weights
+
+        MCPD solver scales prediction errors as follows
+            Error(P) = ||W*(y-P*x)||^2
+        where
+            x is a system state at time t
+            y is a system state at time t+1
+            P is a transition matrix
+            W is a diagonal scaling matrix
+
+        By default, weights are chosen in order  to  minimize  relative prediction
+        error instead of absolute one. For example, if one component of  state  is
+        about 0.5 in magnitude and another one is about 0.05, then algorithm  will
+        make corresponding weights equal to 2.0 and 20.0.
+
+        INPUT PARAMETERS:
+            S       -   solver
+            PW      -   array[N], weights:
+                        * must be non-negative values (exception will be thrown otherwise)
+                        * zero values will be replaced by automatically chosen values
+
+          -- ALGLIB --
+             Copyright 23.05.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mcpdsetpredictionweights(mcpdstate s,
+            double[] pw)
+        {
+            int i = 0;
+            int n = 0;
+
+            n = s.n;
+            alglib.ap.assert(alglib.ap.len(pw)>=n, "MCPDSetPredictionWeights: Length(PW)<N");
+            for(i=0; i<=n-1; i++)
+            {
+                alglib.ap.assert(math.isfinite(pw[i]), "MCPDSetPredictionWeights: PW containts infinite or NAN elements");
+                alglib.ap.assert((double)(pw[i])>=(double)(0), "MCPDSetPredictionWeights: PW containts negative elements");
+                s.pw[i] = pw[i];
+            }
+        }
+
+
+        /*************************************************************************
+        This function is used to start solution of the MCPD problem.
+
+        After return from this function, you can use MCPDResults() to get solution
+        and completion code.
+
+          -- ALGLIB --
+             Copyright 23.05.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mcpdsolve(mcpdstate s)
+        {
+            int n = 0;
+            int npairs = 0;
+            int ccnt = 0;
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int k2 = 0;
+            double v = 0;
+            double vv = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            n = s.n;
+            npairs = s.npairs;
+            
+            //
+            // init fields of S
+            //
+            s.repterminationtype = 0;
+            s.repinneriterationscount = 0;
+            s.repouteriterationscount = 0;
+            s.repnfev = 0;
+            for(k=0; k<=n-1; k++)
+            {
+                for(k2=0; k2<=n-1; k2++)
+                {
+                    s.p[k,k2] = Double.NaN;
+                }
+            }
+            
+            //
+            // Generate "effective" weights for prediction and calculate preconditioner
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                if( (double)(s.pw[i])==(double)(0) )
+                {
+                    v = 0;
+                    k = 0;
+                    for(j=0; j<=npairs-1; j++)
+                    {
+                        if( (double)(s.data[j,n+i])!=(double)(0) )
+                        {
+                            v = v+s.data[j,n+i];
+                            k = k+1;
+                        }
+                    }
+                    if( k!=0 )
+                    {
+                        s.effectivew[i] = k/v;
+                    }
+                    else
+                    {
+                        s.effectivew[i] = 1.0;
+                    }
+                }
+                else
+                {
+                    s.effectivew[i] = s.pw[i];
+                }
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                for(j=0; j<=n-1; j++)
+                {
+                    s.h[i*n+j] = 2*s.regterm;
+                }
+            }
+            for(k=0; k<=npairs-1; k++)
+            {
+                for(i=0; i<=n-1; i++)
+                {
+                    for(j=0; j<=n-1; j++)
+                    {
+                        s.h[i*n+j] = s.h[i*n+j]+2*math.sqr(s.effectivew[i])*math.sqr(s.data[k,j]);
+                    }
+                }
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                for(j=0; j<=n-1; j++)
+                {
+                    if( (double)(s.h[i*n+j])==(double)(0) )
+                    {
+                        s.h[i*n+j] = 1;
+                    }
+                }
+            }
+            
+            //
+            // Generate "effective" BndL/BndU
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                for(j=0; j<=n-1; j++)
+                {
+                    
+                    //
+                    // Set default boundary constraints.
+                    // Lower bound is always zero, upper bound is calculated
+                    // with respect to entry/exit states.
+                    //
+                    s.effectivebndl[i*n+j] = 0.0;
+                    if( s.states[i]>0 || s.states[j]<0 )
+                    {
+                        s.effectivebndu[i*n+j] = 0.0;
+                    }
+                    else
+                    {
+                        s.effectivebndu[i*n+j] = 1.0;
+                    }
+                    
+                    //
+                    // Calculate intersection of the default and user-specified bound constraints.
+                    // This code checks consistency of such combination.
+                    //
+                    if( math.isfinite(s.bndl[i,j]) && (double)(s.bndl[i,j])>(double)(s.effectivebndl[i*n+j]) )
+                    {
+                        s.effectivebndl[i*n+j] = s.bndl[i,j];
+                    }
+                    if( math.isfinite(s.bndu[i,j]) && (double)(s.bndu[i,j])<(double)(s.effectivebndu[i*n+j]) )
+                    {
+                        s.effectivebndu[i*n+j] = s.bndu[i,j];
+                    }
+                    if( (double)(s.effectivebndl[i*n+j])>(double)(s.effectivebndu[i*n+j]) )
+                    {
+                        s.repterminationtype = -3;
+                        return;
+                    }
+                    
+                    //
+                    // Calculate intersection of the effective bound constraints
+                    // and user-specified equality constraints.
+                    // This code checks consistency of such combination.
+                    //
+                    if( math.isfinite(s.ec[i,j]) )
+                    {
+                        if( (double)(s.ec[i,j])<(double)(s.effectivebndl[i*n+j]) || (double)(s.ec[i,j])>(double)(s.effectivebndu[i*n+j]) )
+                        {
+                            s.repterminationtype = -3;
+                            return;
+                        }
+                        s.effectivebndl[i*n+j] = s.ec[i,j];
+                        s.effectivebndu[i*n+j] = s.ec[i,j];
+                    }
+                }
+            }
+            
+            //
+            // Generate linear constraints:
+            // * "default" sums-to-one constraints (not generated for "exit" states)
+            //
+            apserv.rmatrixsetlengthatleast(ref s.effectivec, s.ccnt+n, n*n+1);
+            apserv.ivectorsetlengthatleast(ref s.effectivect, s.ccnt+n);
+            ccnt = s.ccnt;
+            for(i=0; i<=s.ccnt-1; i++)
+            {
+                for(j=0; j<=n*n; j++)
+                {
+                    s.effectivec[i,j] = s.c[i,j];
+                }
+                s.effectivect[i] = s.ct[i];
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                if( s.states[i]>=0 )
+                {
+                    for(k=0; k<=n*n-1; k++)
+                    {
+                        s.effectivec[ccnt,k] = 0;
+                    }
+                    for(k=0; k<=n-1; k++)
+                    {
+                        s.effectivec[ccnt,k*n+i] = 1;
+                    }
+                    s.effectivec[ccnt,n*n] = 1.0;
+                    s.effectivect[ccnt] = 0;
+                    ccnt = ccnt+1;
+                }
+            }
+            
+            //
+            // create optimizer
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                for(j=0; j<=n-1; j++)
+                {
+                    s.tmpp[i*n+j] = (double)1/(double)n;
+                }
+            }
+            minbleic.minbleicrestartfrom(s.bs, s.tmpp);
+            minbleic.minbleicsetbc(s.bs, s.effectivebndl, s.effectivebndu);
+            minbleic.minbleicsetlc(s.bs, s.effectivec, s.effectivect, ccnt);
+            minbleic.minbleicsetinnercond(s.bs, 0, 0, xtol);
+            minbleic.minbleicsetoutercond(s.bs, xtol, 1.0E-5);
+            minbleic.minbleicsetprecdiag(s.bs, s.h);
+            
+            //
+            // solve problem
+            //
+            while( minbleic.minbleiciteration(s.bs) )
+            {
+                alglib.ap.assert(s.bs.needfg, "MCPDSolve: internal error");
+                if( s.bs.needfg )
+                {
+                    
+                    //
+                    // Calculate regularization term
+                    //
+                    s.bs.f = 0.0;
+                    vv = s.regterm;
+                    for(i=0; i<=n-1; i++)
+                    {
+                        for(j=0; j<=n-1; j++)
+                        {
+                            s.bs.f = s.bs.f+vv*math.sqr(s.bs.x[i*n+j]-s.priorp[i,j]);
+                            s.bs.g[i*n+j] = 2*vv*(s.bs.x[i*n+j]-s.priorp[i,j]);
+                        }
+                    }
+                    
+                    //
+                    // calculate prediction error/gradient for K-th pair
+                    //
+                    for(k=0; k<=npairs-1; k++)
+                    {
+                        for(i=0; i<=n-1; i++)
+                        {
+                            i1_ = (0)-(i*n);
+                            v = 0.0;
+                            for(i_=i*n; i_<=i*n+n-1;i_++)
+                            {
+                                v += s.bs.x[i_]*s.data[k,i_+i1_];
+                            }
+                            vv = s.effectivew[i];
+                            s.bs.f = s.bs.f+math.sqr(vv*(v-s.data[k,n+i]));
+                            for(j=0; j<=n-1; j++)
+                            {
+                                s.bs.g[i*n+j] = s.bs.g[i*n+j]+2*vv*vv*(v-s.data[k,n+i])*s.data[k,j];
+                            }
+                        }
+                    }
+                    
+                    //
+                    // continue
+                    //
+                    continue;
+                }
+            }
+            minbleic.minbleicresultsbuf(s.bs, ref s.tmpp, s.br);
+            for(i=0; i<=n-1; i++)
+            {
+                for(j=0; j<=n-1; j++)
+                {
+                    s.p[i,j] = s.tmpp[i*n+j];
+                }
+            }
+            s.repterminationtype = s.br.terminationtype;
+            s.repinneriterationscount = s.br.inneriterationscount;
+            s.repouteriterationscount = s.br.outeriterationscount;
+            s.repnfev = s.br.nfev;
+        }
+
+
+        /*************************************************************************
+        MCPD results
+
+        INPUT PARAMETERS:
+            State   -   algorithm state
+
+        OUTPUT PARAMETERS:
+            P       -   array[N,N], transition matrix
+            Rep     -   optimization report. You should check Rep.TerminationType
+                        in  order  to  distinguish  successful  termination  from
+                        unsuccessful one. Speaking short, positive values  denote
+                        success, negative ones are failures.
+                        More information about fields of this  structure  can  be
+                        found in the comments on MCPDReport datatype.
+
+
+          -- ALGLIB --
+             Copyright 23.05.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mcpdresults(mcpdstate s,
+            ref double[,] p,
+            mcpdreport rep)
+        {
+            int i = 0;
+            int j = 0;
+
+            p = new double[0,0];
+
+            p = new double[s.n, s.n];
+            for(i=0; i<=s.n-1; i++)
+            {
+                for(j=0; j<=s.n-1; j++)
+                {
+                    p[i,j] = s.p[i,j];
+                }
+            }
+            rep.terminationtype = s.repterminationtype;
+            rep.inneriterationscount = s.repinneriterationscount;
+            rep.outeriterationscount = s.repouteriterationscount;
+            rep.nfev = s.repnfev;
+        }
+
+
+        /*************************************************************************
+        Internal initialization function
+
+          -- ALGLIB --
+             Copyright 23.05.2010 by Bochkanov Sergey
+        *************************************************************************/
+        private static void mcpdinit(int n,
+            int entrystate,
+            int exitstate,
+            mcpdstate s)
+        {
+            int i = 0;
+            int j = 0;
+
+            alglib.ap.assert(n>=1, "MCPDCreate: N<1");
+            s.n = n;
+            s.states = new int[n];
+            for(i=0; i<=n-1; i++)
+            {
+                s.states[i] = 0;
+            }
+            if( entrystate>=0 )
+            {
+                s.states[entrystate] = 1;
+            }
+            if( exitstate>=0 )
+            {
+                s.states[exitstate] = -1;
+            }
+            s.npairs = 0;
+            s.regterm = 1.0E-8;
+            s.ccnt = 0;
+            s.p = new double[n, n];
+            s.ec = new double[n, n];
+            s.bndl = new double[n, n];
+            s.bndu = new double[n, n];
+            s.pw = new double[n];
+            s.priorp = new double[n, n];
+            s.tmpp = new double[n*n];
+            s.effectivew = new double[n];
+            s.effectivebndl = new double[n*n];
+            s.effectivebndu = new double[n*n];
+            s.h = new double[n*n];
+            for(i=0; i<=n-1; i++)
+            {
+                for(j=0; j<=n-1; j++)
+                {
+                    s.p[i,j] = 0.0;
+                    s.priorp[i,j] = 0.0;
+                    s.bndl[i,j] = Double.NegativeInfinity;
+                    s.bndu[i,j] = Double.PositiveInfinity;
+                    s.ec[i,j] = Double.NaN;
+                }
+                s.pw[i] = 0.0;
+                s.priorp[i,i] = 1.0;
+            }
+            s.data = new double[1, 2*n];
+            for(i=0; i<=2*n-1; i++)
+            {
+                s.data[0,i] = 0.0;
+            }
+            for(i=0; i<=n*n-1; i++)
+            {
+                s.tmpp[i] = 0.0;
+            }
+            minbleic.minbleiccreate(n*n, s.tmpp, s.bs);
+        }
+
+
+    }
+    public class mlptrain
+    {
+        /*************************************************************************
+        Training report:
+            * NGrad     - number of gradient calculations
+            * NHess     - number of Hessian calculations
+            * NCholesky - number of Cholesky decompositions
+        *************************************************************************/
+        public class mlpreport
+        {
+            public int ngrad;
+            public int nhess;
+            public int ncholesky;
+        };
+
+
+        /*************************************************************************
+        Cross-validation estimates of generalization error
+        *************************************************************************/
+        public class mlpcvreport
+        {
+            public double relclserror;
+            public double avgce;
+            public double rmserror;
+            public double avgerror;
+            public double avgrelerror;
+        };
+
+
+
+
+        public const double mindecay = 0.001;
+
+
+        /*************************************************************************
+        Neural network training  using  modified  Levenberg-Marquardt  with  exact
+        Hessian calculation and regularization. Subroutine trains  neural  network
+        with restarts from random positions. Algorithm is well  suited  for  small
+        and medium scale problems (hundreds of weights).
+
+        INPUT PARAMETERS:
+            Network     -   neural network with initialized geometry
+            XY          -   training set
+            NPoints     -   training set size
+            Decay       -   weight decay constant, >=0.001
+                            Decay term 'Decay*||Weights||^2' is added to error
+                            function.
+                            If you don't know what Decay to choose, use 0.001.
+            Restarts    -   number of restarts from random position, >0.
+                            If you don't know what Restarts to choose, use 2.
+
+        OUTPUT PARAMETERS:
+            Network     -   trained neural network.
+            Info        -   return code:
+                            * -9, if internal matrix inverse subroutine failed
+                            * -2, if there is a point with class number
+                                  outside of [0..NOut-1].
+                            * -1, if wrong parameters specified
+                                  (NPoints<0, Restarts<1).
+                            *  2, if task has been solved.
+            Rep         -   training report
+
+          -- ALGLIB --
+             Copyright 10.03.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlptrainlm(mlpbase.multilayerperceptron network,
+            double[,] xy,
+            int npoints,
+            double decay,
+            int restarts,
+            ref int info,
+            mlpreport rep)
+        {
+            int nin = 0;
+            int nout = 0;
+            int wcount = 0;
+            double lmftol = 0;
+            double lmsteptol = 0;
+            int i = 0;
+            int k = 0;
+            double v = 0;
+            double e = 0;
+            double enew = 0;
+            double xnorm2 = 0;
+            double stepnorm = 0;
+            double[] g = new double[0];
+            double[] d = new double[0];
+            double[,] h = new double[0,0];
+            double[,] hmod = new double[0,0];
+            double[,] z = new double[0,0];
+            bool spd = new bool();
+            double nu = 0;
+            double lambdav = 0;
+            double lambdaup = 0;
+            double lambdadown = 0;
+            minlbfgs.minlbfgsreport internalrep = new minlbfgs.minlbfgsreport();
+            minlbfgs.minlbfgsstate state = new minlbfgs.minlbfgsstate();
+            double[] x = new double[0];
+            double[] y = new double[0];
+            double[] wbase = new double[0];
+            double[] wdir = new double[0];
+            double[] wt = new double[0];
+            double[] wx = new double[0];
+            int pass = 0;
+            double[] wbest = new double[0];
+            double ebest = 0;
+            int invinfo = 0;
+            matinv.matinvreport invrep = new matinv.matinvreport();
+            int solverinfo = 0;
+            densesolver.densesolverreport solverrep = new densesolver.densesolverreport();
+            int i_ = 0;
+
+            info = 0;
+
+            mlpbase.mlpproperties(network, ref nin, ref nout, ref wcount);
+            lambdaup = 10;
+            lambdadown = 0.3;
+            lmftol = 0.001;
+            lmsteptol = 0.001;
+            
+            //
+            // Test for inputs
+            //
+            if( npoints<=0 || restarts<1 )
+            {
+                info = -1;
+                return;
+            }
+            if( mlpbase.mlpissoftmax(network) )
+            {
+                for(i=0; i<=npoints-1; i++)
+                {
+                    if( (int)Math.Round(xy[i,nin])<0 || (int)Math.Round(xy[i,nin])>=nout )
+                    {
+                        info = -2;
+                        return;
+                    }
+                }
+            }
+            decay = Math.Max(decay, mindecay);
+            info = 2;
+            
+            //
+            // Initialize data
+            //
+            rep.ngrad = 0;
+            rep.nhess = 0;
+            rep.ncholesky = 0;
+            
+            //
+            // General case.
+            // Prepare task and network. Allocate space.
+            //
+            mlpbase.mlpinitpreprocessor(network, xy, npoints);
+            g = new double[wcount-1+1];
+            h = new double[wcount-1+1, wcount-1+1];
+            hmod = new double[wcount-1+1, wcount-1+1];
+            wbase = new double[wcount-1+1];
+            wdir = new double[wcount-1+1];
+            wbest = new double[wcount-1+1];
+            wt = new double[wcount-1+1];
+            wx = new double[wcount-1+1];
+            ebest = math.maxrealnumber;
+            
+            //
+            // Multiple passes
+            //
+            for(pass=1; pass<=restarts; pass++)
+            {
+                
+                //
+                // Initialize weights
+                //
+                mlpbase.mlprandomize(network);
+                
+                //
+                // First stage of the hybrid algorithm: LBFGS
+                //
+                for(i_=0; i_<=wcount-1;i_++)
+                {
+                    wbase[i_] = network.weights[i_];
+                }
+                minlbfgs.minlbfgscreate(wcount, Math.Min(wcount, 5), wbase, state);
+                minlbfgs.minlbfgssetcond(state, 0, 0, 0, Math.Max(25, wcount));
+                while( minlbfgs.minlbfgsiteration(state) )
+                {
+                    
+                    //
+                    // gradient
+                    //
+                    for(i_=0; i_<=wcount-1;i_++)
+                    {
+                        network.weights[i_] = state.x[i_];
+                    }
+                    mlpbase.mlpgradbatch(network, xy, npoints, ref state.f, ref state.g);
+                    
+                    //
+                    // weight decay
+                    //
+                    v = 0.0;
+                    for(i_=0; i_<=wcount-1;i_++)
+                    {
+                        v += network.weights[i_]*network.weights[i_];
+                    }
+                    state.f = state.f+0.5*decay*v;
+                    for(i_=0; i_<=wcount-1;i_++)
+                    {
+                        state.g[i_] = state.g[i_] + decay*network.weights[i_];
+                    }
+                    
+                    //
+                    // next iteration
+                    //
+                    rep.ngrad = rep.ngrad+1;
+                }
+                minlbfgs.minlbfgsresults(state, ref wbase, internalrep);
+                for(i_=0; i_<=wcount-1;i_++)
+                {
+                    network.weights[i_] = wbase[i_];
+                }
+                
+                //
+                // Second stage of the hybrid algorithm: LM
+                //
+                // Initialize H with identity matrix,
+                // G with gradient,
+                // E with regularized error.
+                //
+                mlpbase.mlphessianbatch(network, xy, npoints, ref e, ref g, ref h);
+                v = 0.0;
+                for(i_=0; i_<=wcount-1;i_++)
+                {
+                    v += network.weights[i_]*network.weights[i_];
+                }
+                e = e+0.5*decay*v;
+                for(i_=0; i_<=wcount-1;i_++)
+                {
+                    g[i_] = g[i_] + decay*network.weights[i_];
+                }
+                for(k=0; k<=wcount-1; k++)
+                {
+                    h[k,k] = h[k,k]+decay;
+                }
+                rep.nhess = rep.nhess+1;
+                lambdav = 0.001;
+                nu = 2;
+                while( true )
+                {
+                    
+                    //
+                    // 1. HMod = H+lambda*I
+                    // 2. Try to solve (H+Lambda*I)*dx = -g.
+                    //    Increase lambda if left part is not positive definite.
+                    //
+                    for(i=0; i<=wcount-1; i++)
+                    {
+                        for(i_=0; i_<=wcount-1;i_++)
+                        {
+                            hmod[i,i_] = h[i,i_];
+                        }
+                        hmod[i,i] = hmod[i,i]+lambdav;
+                    }
+                    spd = trfac.spdmatrixcholesky(ref hmod, wcount, true);
+                    rep.ncholesky = rep.ncholesky+1;
+                    if( !spd )
+                    {
+                        lambdav = lambdav*lambdaup*nu;
+                        nu = nu*2;
+                        continue;
+                    }
+                    densesolver.spdmatrixcholeskysolve(hmod, wcount, true, g, ref solverinfo, solverrep, ref wdir);
+                    if( solverinfo<0 )
+                    {
+                        lambdav = lambdav*lambdaup*nu;
+                        nu = nu*2;
+                        continue;
+                    }
+                    for(i_=0; i_<=wcount-1;i_++)
+                    {
+                        wdir[i_] = -1*wdir[i_];
+                    }
+                    
+                    //
+                    // Lambda found.
+                    // 1. Save old w in WBase
+                    // 1. Test some stopping criterions
+                    // 2. If error(w+wdir)>error(w), increase lambda
+                    //
+                    for(i_=0; i_<=wcount-1;i_++)
+                    {
+                        network.weights[i_] = network.weights[i_] + wdir[i_];
+                    }
+                    xnorm2 = 0.0;
+                    for(i_=0; i_<=wcount-1;i_++)
+                    {
+                        xnorm2 += network.weights[i_]*network.weights[i_];
+                    }
+                    stepnorm = 0.0;
+                    for(i_=0; i_<=wcount-1;i_++)
+                    {
+                        stepnorm += wdir[i_]*wdir[i_];
+                    }
+                    stepnorm = Math.Sqrt(stepnorm);
+                    enew = mlpbase.mlperror(network, xy, npoints)+0.5*decay*xnorm2;
+                    if( (double)(stepnorm)<(double)(lmsteptol*(1+Math.Sqrt(xnorm2))) )
+                    {
+                        break;
+                    }
+                    if( (double)(enew)>(double)(e) )
+                    {
+                        lambdav = lambdav*lambdaup*nu;
+                        nu = nu*2;
+                        continue;
+                    }
+                    
+                    //
+                    // Optimize using inv(cholesky(H)) as preconditioner
+                    //
+                    matinv.rmatrixtrinverse(ref hmod, wcount, true, false, ref invinfo, invrep);
+                    if( invinfo<=0 )
+                    {
+                        
+                        //
+                        // if matrix can't be inverted then exit with errors
+                        // TODO: make WCount steps in direction suggested by HMod
+                        //
+                        info = -9;
+                        return;
+                    }
+                    for(i_=0; i_<=wcount-1;i_++)
+                    {
+                        wbase[i_] = network.weights[i_];
+                    }
+                    for(i=0; i<=wcount-1; i++)
+                    {
+                        wt[i] = 0;
+                    }
+                    minlbfgs.minlbfgscreatex(wcount, wcount, wt, 1, 0.0, state);
+                    minlbfgs.minlbfgssetcond(state, 0, 0, 0, 5);
+                    while( minlbfgs.minlbfgsiteration(state) )
+                    {
+                        
+                        //
+                        // gradient
+                        //
+                        for(i=0; i<=wcount-1; i++)
+                        {
+                            v = 0.0;
+                            for(i_=i; i_<=wcount-1;i_++)
+                            {
+                                v += state.x[i_]*hmod[i,i_];
+                            }
+                            network.weights[i] = wbase[i]+v;
+                        }
+                        mlpbase.mlpgradbatch(network, xy, npoints, ref state.f, ref g);
+                        for(i=0; i<=wcount-1; i++)
+                        {
+                            state.g[i] = 0;
+                        }
+                        for(i=0; i<=wcount-1; i++)
+                        {
+                            v = g[i];
+                            for(i_=i; i_<=wcount-1;i_++)
+                            {
+                                state.g[i_] = state.g[i_] + v*hmod[i,i_];
+                            }
+                        }
+                        
+                        //
+                        // weight decay
+                        // grad(x'*x) = A'*(x0+A*t)
+                        //
+                        v = 0.0;
+                        for(i_=0; i_<=wcount-1;i_++)
+                        {
+                            v += network.weights[i_]*network.weights[i_];
+                        }
+                        state.f = state.f+0.5*decay*v;
+                        for(i=0; i<=wcount-1; i++)
+                        {
+                            v = decay*network.weights[i];
+                            for(i_=i; i_<=wcount-1;i_++)
+                            {
+                                state.g[i_] = state.g[i_] + v*hmod[i,i_];
+                            }
+                        }
+                        
+                        //
+                        // next iteration
+                        //
+                        rep.ngrad = rep.ngrad+1;
+                    }
+                    minlbfgs.minlbfgsresults(state, ref wt, internalrep);
+                    
+                    //
+                    // Accept new position.
+                    // Calculate Hessian
+                    //
+                    for(i=0; i<=wcount-1; i++)
+                    {
+                        v = 0.0;
+                        for(i_=i; i_<=wcount-1;i_++)
+                        {
+                            v += wt[i_]*hmod[i,i_];
+                        }
+                        network.weights[i] = wbase[i]+v;
+                    }
+                    mlpbase.mlphessianbatch(network, xy, npoints, ref e, ref g, ref h);
+                    v = 0.0;
+                    for(i_=0; i_<=wcount-1;i_++)
+                    {
+                        v += network.weights[i_]*network.weights[i_];
+                    }
+                    e = e+0.5*decay*v;
+                    for(i_=0; i_<=wcount-1;i_++)
+                    {
+                        g[i_] = g[i_] + decay*network.weights[i_];
+                    }
+                    for(k=0; k<=wcount-1; k++)
+                    {
+                        h[k,k] = h[k,k]+decay;
+                    }
+                    rep.nhess = rep.nhess+1;
+                    
+                    //
+                    // Update lambda
+                    //
+                    lambdav = lambdav*lambdadown;
+                    nu = 2;
+                }
+                
+                //
+                // update WBest
+                //
+                v = 0.0;
+                for(i_=0; i_<=wcount-1;i_++)
+                {
+                    v += network.weights[i_]*network.weights[i_];
+                }
+                e = 0.5*decay*v+mlpbase.mlperror(network, xy, npoints);
+                if( (double)(e)<(double)(ebest) )
+                {
+                    ebest = e;
+                    for(i_=0; i_<=wcount-1;i_++)
+                    {
+                        wbest[i_] = network.weights[i_];
+                    }
+                }
+            }
+            
+            //
+            // copy WBest to output
+            //
+            for(i_=0; i_<=wcount-1;i_++)
+            {
+                network.weights[i_] = wbest[i_];
+            }
+        }
+
+
+        /*************************************************************************
+        Neural  network  training  using  L-BFGS  algorithm  with  regularization.
+        Subroutine  trains  neural  network  with  restarts from random positions.
+        Algorithm  is  well  suited  for  problems  of  any dimensionality (memory
+        requirements and step complexity are linear by weights number).
+
+        INPUT PARAMETERS:
+            Network     -   neural network with initialized geometry
+            XY          -   training set
+            NPoints     -   training set size
+            Decay       -   weight decay constant, >=0.001
+                            Decay term 'Decay*||Weights||^2' is added to error
+                            function.
+                            If you don't know what Decay to choose, use 0.001.
+            Restarts    -   number of restarts from random position, >0.
+                            If you don't know what Restarts to choose, use 2.
+            WStep       -   stopping criterion. Algorithm stops if  step  size  is
+                            less than WStep. Recommended value - 0.01.  Zero  step
+                            size means stopping after MaxIts iterations.
+            MaxIts      -   stopping   criterion.  Algorithm  stops  after  MaxIts
+                            iterations (NOT gradient  calculations).  Zero  MaxIts
+                            means stopping when step is sufficiently small.
+
+        OUTPUT PARAMETERS:
+            Network     -   trained neural network.
+            Info        -   return code:
+                            * -8, if both WStep=0 and MaxIts=0
+                            * -2, if there is a point with class number
+                                  outside of [0..NOut-1].
+                            * -1, if wrong parameters specified
+                                  (NPoints<0, Restarts<1).
+                            *  2, if task has been solved.
+            Rep         -   training report
+
+          -- ALGLIB --
+             Copyright 09.12.2007 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlptrainlbfgs(mlpbase.multilayerperceptron network,
+            double[,] xy,
+            int npoints,
+            double decay,
+            int restarts,
+            double wstep,
+            int maxits,
+            ref int info,
+            mlpreport rep)
+        {
+            int i = 0;
+            int pass = 0;
+            int nin = 0;
+            int nout = 0;
+            int wcount = 0;
+            double[] w = new double[0];
+            double[] wbest = new double[0];
+            double e = 0;
+            double v = 0;
+            double ebest = 0;
+            minlbfgs.minlbfgsreport internalrep = new minlbfgs.minlbfgsreport();
+            minlbfgs.minlbfgsstate state = new minlbfgs.minlbfgsstate();
+            int i_ = 0;
+
+            info = 0;
+
+            
+            //
+            // Test inputs, parse flags, read network geometry
+            //
+            if( (double)(wstep)==(double)(0) && maxits==0 )
+            {
+                info = -8;
+                return;
+            }
+            if( ((npoints<=0 || restarts<1) || (double)(wstep)<(double)(0)) || maxits<0 )
+            {
+                info = -1;
+                return;
+            }
+            mlpbase.mlpproperties(network, ref nin, ref nout, ref wcount);
+            if( mlpbase.mlpissoftmax(network) )
+            {
+                for(i=0; i<=npoints-1; i++)
+                {
+                    if( (int)Math.Round(xy[i,nin])<0 || (int)Math.Round(xy[i,nin])>=nout )
+                    {
+                        info = -2;
+                        return;
+                    }
+                }
+            }
+            decay = Math.Max(decay, mindecay);
+            info = 2;
+            
+            //
+            // Prepare
+            //
+            mlpbase.mlpinitpreprocessor(network, xy, npoints);
+            w = new double[wcount-1+1];
+            wbest = new double[wcount-1+1];
+            ebest = math.maxrealnumber;
+            
+            //
+            // Multiple starts
+            //
+            rep.ncholesky = 0;
+            rep.nhess = 0;
+            rep.ngrad = 0;
+            for(pass=1; pass<=restarts; pass++)
+            {
+                
+                //
+                // Process
+                //
+                mlpbase.mlprandomize(network);
+                for(i_=0; i_<=wcount-1;i_++)
+                {
+                    w[i_] = network.weights[i_];
+                }
+                minlbfgs.minlbfgscreate(wcount, Math.Min(wcount, 10), w, state);
+                minlbfgs.minlbfgssetcond(state, 0.0, 0.0, wstep, maxits);
+                while( minlbfgs.minlbfgsiteration(state) )
+                {
+                    for(i_=0; i_<=wcount-1;i_++)
+                    {
+                        network.weights[i_] = state.x[i_];
+                    }
+                    mlpbase.mlpgradnbatch(network, xy, npoints, ref state.f, ref state.g);
+                    v = 0.0;
+                    for(i_=0; i_<=wcount-1;i_++)
+                    {
+                        v += network.weights[i_]*network.weights[i_];
+                    }
+                    state.f = state.f+0.5*decay*v;
+                    for(i_=0; i_<=wcount-1;i_++)
+                    {
+                        state.g[i_] = state.g[i_] + decay*network.weights[i_];
+                    }
+                    rep.ngrad = rep.ngrad+1;
+                }
+                minlbfgs.minlbfgsresults(state, ref w, internalrep);
+                for(i_=0; i_<=wcount-1;i_++)
+                {
+                    network.weights[i_] = w[i_];
+                }
+                
+                //
+                // Compare with best
+                //
+                v = 0.0;
+                for(i_=0; i_<=wcount-1;i_++)
+                {
+                    v += network.weights[i_]*network.weights[i_];
+                }
+                e = mlpbase.mlperrorn(network, xy, npoints)+0.5*decay*v;
+                if( (double)(e)<(double)(ebest) )
+                {
+                    for(i_=0; i_<=wcount-1;i_++)
+                    {
+                        wbest[i_] = network.weights[i_];
+                    }
+                    ebest = e;
+                }
+            }
+            
+            //
+            // The best network
+            //
+            for(i_=0; i_<=wcount-1;i_++)
+            {
+                network.weights[i_] = wbest[i_];
+            }
+        }
+
+
+        /*************************************************************************
+        Neural network training using early stopping (base algorithm - L-BFGS with
+        regularization).
+
+        INPUT PARAMETERS:
+            Network     -   neural network with initialized geometry
+            TrnXY       -   training set
+            TrnSize     -   training set size, TrnSize>0
+            ValXY       -   validation set
+            ValSize     -   validation set size, ValSize>0
+            Decay       -   weight decay constant, >=0.001
+                            Decay term 'Decay*||Weights||^2' is added to error
+                            function.
+                            If you don't know what Decay to choose, use 0.001.
+            Restarts    -   number of restarts, either:
+                            * strictly positive number - algorithm make specified
+                              number of restarts from random position.
+                            * -1, in which case algorithm makes exactly one run
+                              from the initial state of the network (no randomization).
+                            If you don't know what Restarts to choose, choose one
+                            one the following:
+                            * -1 (deterministic start)
+                            * +1 (one random restart)
+                            * +5 (moderate amount of random restarts)
+
+        OUTPUT PARAMETERS:
+            Network     -   trained neural network.
+            Info        -   return code:
+                            * -2, if there is a point with class number
+                                  outside of [0..NOut-1].
+                            * -1, if wrong parameters specified
+                                  (NPoints<0, Restarts<1, ...).
+                            *  2, task has been solved, stopping  criterion  met -
+                                  sufficiently small step size.  Not expected  (we
+                                  use  EARLY  stopping)  but  possible  and not an
+                                  error.
+                            *  6, task has been solved, stopping  criterion  met -
+                                  increasing of validation set error.
+            Rep         -   training report
+
+        NOTE:
+
+        Algorithm stops if validation set error increases for  a  long  enough  or
+        step size is small enought  (there  are  task  where  validation  set  may
+        decrease for eternity). In any case solution returned corresponds  to  the
+        minimum of validation set error.
+
+          -- ALGLIB --
+             Copyright 10.03.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlptraines(mlpbase.multilayerperceptron network,
+            double[,] trnxy,
+            int trnsize,
+            double[,] valxy,
+            int valsize,
+            double decay,
+            int restarts,
+            ref int info,
+            mlpreport rep)
+        {
+            int i = 0;
+            int pass = 0;
+            int nin = 0;
+            int nout = 0;
+            int wcount = 0;
+            double[] w = new double[0];
+            double[] wbest = new double[0];
+            double e = 0;
+            double v = 0;
+            double ebest = 0;
+            double[] wfinal = new double[0];
+            double efinal = 0;
+            int itcnt = 0;
+            int itbest = 0;
+            minlbfgs.minlbfgsreport internalrep = new minlbfgs.minlbfgsreport();
+            minlbfgs.minlbfgsstate state = new minlbfgs.minlbfgsstate();
+            double wstep = 0;
+            bool needrandomization = new bool();
+            int i_ = 0;
+
+            info = 0;
+
+            wstep = 0.001;
+            
+            //
+            // Test inputs, parse flags, read network geometry
+            //
+            if( ((trnsize<=0 || valsize<=0) || (restarts<1 && restarts!=-1)) || (double)(decay)<(double)(0) )
+            {
+                info = -1;
+                return;
+            }
+            if( restarts==-1 )
+            {
+                needrandomization = false;
+                restarts = 1;
+            }
+            else
+            {
+                needrandomization = true;
+            }
+            mlpbase.mlpproperties(network, ref nin, ref nout, ref wcount);
+            if( mlpbase.mlpissoftmax(network) )
+            {
+                for(i=0; i<=trnsize-1; i++)
+                {
+                    if( (int)Math.Round(trnxy[i,nin])<0 || (int)Math.Round(trnxy[i,nin])>=nout )
+                    {
+                        info = -2;
+                        return;
+                    }
+                }
+                for(i=0; i<=valsize-1; i++)
+                {
+                    if( (int)Math.Round(valxy[i,nin])<0 || (int)Math.Round(valxy[i,nin])>=nout )
+                    {
+                        info = -2;
+                        return;
+                    }
+                }
+            }
+            info = 2;
+            
+            //
+            // Prepare
+            //
+            mlpbase.mlpinitpreprocessor(network, trnxy, trnsize);
+            w = new double[wcount-1+1];
+            wbest = new double[wcount-1+1];
+            wfinal = new double[wcount-1+1];
+            efinal = math.maxrealnumber;
+            for(i=0; i<=wcount-1; i++)
+            {
+                wfinal[i] = 0;
+            }
+            
+            //
+            // Multiple starts
+            //
+            rep.ncholesky = 0;
+            rep.nhess = 0;
+            rep.ngrad = 0;
+            for(pass=1; pass<=restarts; pass++)
+            {
+                
+                //
+                // Process
+                //
+                if( needrandomization )
+                {
+                    mlpbase.mlprandomize(network);
+                }
+                ebest = mlpbase.mlperror(network, valxy, valsize);
+                for(i_=0; i_<=wcount-1;i_++)
+                {
+                    wbest[i_] = network.weights[i_];
+                }
+                itbest = 0;
+                itcnt = 0;
+                for(i_=0; i_<=wcount-1;i_++)
+                {
+                    w[i_] = network.weights[i_];
+                }
+                minlbfgs.minlbfgscreate(wcount, Math.Min(wcount, 10), w, state);
+                minlbfgs.minlbfgssetcond(state, 0.0, 0.0, wstep, 0);
+                minlbfgs.minlbfgssetxrep(state, true);
+                while( minlbfgs.minlbfgsiteration(state) )
+                {
+                    
+                    //
+                    // Calculate gradient
+                    //
+                    if( state.needfg )
+                    {
+                        for(i_=0; i_<=wcount-1;i_++)
+                        {
+                            network.weights[i_] = state.x[i_];
+                        }
+                        mlpbase.mlpgradnbatch(network, trnxy, trnsize, ref state.f, ref state.g);
+                        v = 0.0;
+                        for(i_=0; i_<=wcount-1;i_++)
+                        {
+                            v += network.weights[i_]*network.weights[i_];
+                        }
+                        state.f = state.f+0.5*decay*v;
+                        for(i_=0; i_<=wcount-1;i_++)
+                        {
+                            state.g[i_] = state.g[i_] + decay*network.weights[i_];
+                        }
+                        rep.ngrad = rep.ngrad+1;
+                    }
+                    
+                    //
+                    // Validation set
+                    //
+                    if( state.xupdated )
+                    {
+                        for(i_=0; i_<=wcount-1;i_++)
+                        {
+                            network.weights[i_] = state.x[i_];
+                        }
+                        e = mlpbase.mlperror(network, valxy, valsize);
+                        if( (double)(e)<(double)(ebest) )
+                        {
+                            ebest = e;
+                            for(i_=0; i_<=wcount-1;i_++)
+                            {
+                                wbest[i_] = network.weights[i_];
+                            }
+                            itbest = itcnt;
+                        }
+                        if( itcnt>30 && (double)(itcnt)>(double)(1.5*itbest) )
+                        {
+                            info = 6;
+                            break;
+                        }
+                        itcnt = itcnt+1;
+                    }
+                }
+                minlbfgs.minlbfgsresults(state, ref w, internalrep);
+                
+                //
+                // Compare with final answer
+                //
+                if( (double)(ebest)<(double)(efinal) )
+                {
+                    for(i_=0; i_<=wcount-1;i_++)
+                    {
+                        wfinal[i_] = wbest[i_];
+                    }
+                    efinal = ebest;
+                }
+            }
+            
+            //
+            // The best network
+            //
+            for(i_=0; i_<=wcount-1;i_++)
+            {
+                network.weights[i_] = wfinal[i_];
+            }
+        }
+
+
+        /*************************************************************************
+        Cross-validation estimate of generalization error.
+
+        Base algorithm - L-BFGS.
+
+        INPUT PARAMETERS:
+            Network     -   neural network with initialized geometry.   Network is
+                            not changed during cross-validation -  it is used only
+                            as a representative of its architecture.
+            XY          -   training set.
+            SSize       -   training set size
+            Decay       -   weight  decay, same as in MLPTrainLBFGS
+            Restarts    -   number of restarts, >0.
+                            restarts are counted for each partition separately, so
+                            total number of restarts will be Restarts*FoldsCount.
+            WStep       -   stopping criterion, same as in MLPTrainLBFGS
+            MaxIts      -   stopping criterion, same as in MLPTrainLBFGS
+            FoldsCount  -   number of folds in k-fold cross-validation,
+                            2<=FoldsCount<=SSize.
+                            recommended value: 10.
+
+        OUTPUT PARAMETERS:
+            Info        -   return code, same as in MLPTrainLBFGS
+            Rep         -   report, same as in MLPTrainLM/MLPTrainLBFGS
+            CVRep       -   generalization error estimates
+
+          -- ALGLIB --
+             Copyright 09.12.2007 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpkfoldcvlbfgs(mlpbase.multilayerperceptron network,
+            double[,] xy,
+            int npoints,
+            double decay,
+            int restarts,
+            double wstep,
+            int maxits,
+            int foldscount,
+            ref int info,
+            mlpreport rep,
+            mlpcvreport cvrep)
+        {
+            info = 0;
+
+            mlpkfoldcvgeneral(network, xy, npoints, decay, restarts, foldscount, false, wstep, maxits, ref info, rep, cvrep);
+        }
+
+
+        /*************************************************************************
+        Cross-validation estimate of generalization error.
+
+        Base algorithm - Levenberg-Marquardt.
+
+        INPUT PARAMETERS:
+            Network     -   neural network with initialized geometry.   Network is
+                            not changed during cross-validation -  it is used only
+                            as a representative of its architecture.
+            XY          -   training set.
+            SSize       -   training set size
+            Decay       -   weight  decay, same as in MLPTrainLBFGS
+            Restarts    -   number of restarts, >0.
+                            restarts are counted for each partition separately, so
+                            total number of restarts will be Restarts*FoldsCount.
+            FoldsCount  -   number of folds in k-fold cross-validation,
+                            2<=FoldsCount<=SSize.
+                            recommended value: 10.
+
+        OUTPUT PARAMETERS:
+            Info        -   return code, same as in MLPTrainLBFGS
+            Rep         -   report, same as in MLPTrainLM/MLPTrainLBFGS
+            CVRep       -   generalization error estimates
+
+          -- ALGLIB --
+             Copyright 09.12.2007 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpkfoldcvlm(mlpbase.multilayerperceptron network,
+            double[,] xy,
+            int npoints,
+            double decay,
+            int restarts,
+            int foldscount,
+            ref int info,
+            mlpreport rep,
+            mlpcvreport cvrep)
+        {
+            info = 0;
+
+            mlpkfoldcvgeneral(network, xy, npoints, decay, restarts, foldscount, true, 0.0, 0, ref info, rep, cvrep);
+        }
+
+
+        /*************************************************************************
+        Internal cross-validation subroutine
+        *************************************************************************/
+        private static void mlpkfoldcvgeneral(mlpbase.multilayerperceptron n,
+            double[,] xy,
+            int npoints,
+            double decay,
+            int restarts,
+            int foldscount,
+            bool lmalgorithm,
+            double wstep,
+            int maxits,
+            ref int info,
+            mlpreport rep,
+            mlpcvreport cvrep)
+        {
+            int i = 0;
+            int fold = 0;
+            int j = 0;
+            int k = 0;
+            mlpbase.multilayerperceptron network = new mlpbase.multilayerperceptron();
+            int nin = 0;
+            int nout = 0;
+            int rowlen = 0;
+            int wcount = 0;
+            int nclasses = 0;
+            int tssize = 0;
+            int cvssize = 0;
+            double[,] cvset = new double[0,0];
+            double[,] testset = new double[0,0];
+            int[] folds = new int[0];
+            int relcnt = 0;
+            mlpreport internalrep = new mlpreport();
+            double[] x = new double[0];
+            double[] y = new double[0];
+            int i_ = 0;
+
+            info = 0;
+
+            
+            //
+            // Read network geometry, test parameters
+            //
+            mlpbase.mlpproperties(n, ref nin, ref nout, ref wcount);
+            if( mlpbase.mlpissoftmax(n) )
+            {
+                nclasses = nout;
+                rowlen = nin+1;
+            }
+            else
+            {
+                nclasses = -nout;
+                rowlen = nin+nout;
+            }
+            if( (npoints<=0 || foldscount<2) || foldscount>npoints )
+            {
+                info = -1;
+                return;
+            }
+            mlpbase.mlpcopy(n, network);
+            
+            //
+            // K-fold out cross-validation.
+            // First, estimate generalization error
+            //
+            testset = new double[npoints-1+1, rowlen-1+1];
+            cvset = new double[npoints-1+1, rowlen-1+1];
+            x = new double[nin-1+1];
+            y = new double[nout-1+1];
+            mlpkfoldsplit(xy, npoints, nclasses, foldscount, false, ref folds);
+            cvrep.relclserror = 0;
+            cvrep.avgce = 0;
+            cvrep.rmserror = 0;
+            cvrep.avgerror = 0;
+            cvrep.avgrelerror = 0;
+            rep.ngrad = 0;
+            rep.nhess = 0;
+            rep.ncholesky = 0;
+            relcnt = 0;
+            for(fold=0; fold<=foldscount-1; fold++)
+            {
+                
+                //
+                // Separate set
+                //
+                tssize = 0;
+                cvssize = 0;
+                for(i=0; i<=npoints-1; i++)
+                {
+                    if( folds[i]==fold )
+                    {
+                        for(i_=0; i_<=rowlen-1;i_++)
+                        {
+                            testset[tssize,i_] = xy[i,i_];
+                        }
+                        tssize = tssize+1;
+                    }
+                    else
+                    {
+                        for(i_=0; i_<=rowlen-1;i_++)
+                        {
+                            cvset[cvssize,i_] = xy[i,i_];
+                        }
+                        cvssize = cvssize+1;
+                    }
+                }
+                
+                //
+                // Train on CV training set
+                //
+                if( lmalgorithm )
+                {
+                    mlptrainlm(network, cvset, cvssize, decay, restarts, ref info, internalrep);
+                }
+                else
+                {
+                    mlptrainlbfgs(network, cvset, cvssize, decay, restarts, wstep, maxits, ref info, internalrep);
+                }
+                if( info<0 )
+                {
+                    cvrep.relclserror = 0;
+                    cvrep.avgce = 0;
+                    cvrep.rmserror = 0;
+                    cvrep.avgerror = 0;
+                    cvrep.avgrelerror = 0;
+                    return;
+                }
+                rep.ngrad = rep.ngrad+internalrep.ngrad;
+                rep.nhess = rep.nhess+internalrep.nhess;
+                rep.ncholesky = rep.ncholesky+internalrep.ncholesky;
+                
+                //
+                // Estimate error using CV test set
+                //
+                if( mlpbase.mlpissoftmax(network) )
+                {
+                    
+                    //
+                    // classification-only code
+                    //
+                    cvrep.relclserror = cvrep.relclserror+mlpbase.mlpclserror(network, testset, tssize);
+                    cvrep.avgce = cvrep.avgce+mlpbase.mlperrorn(network, testset, tssize);
+                }
+                for(i=0; i<=tssize-1; i++)
+                {
+                    for(i_=0; i_<=nin-1;i_++)
+                    {
+                        x[i_] = testset[i,i_];
+                    }
+                    mlpbase.mlpprocess(network, x, ref y);
+                    if( mlpbase.mlpissoftmax(network) )
+                    {
+                        
+                        //
+                        // Classification-specific code
+                        //
+                        k = (int)Math.Round(testset[i,nin]);
+                        for(j=0; j<=nout-1; j++)
+                        {
+                            if( j==k )
+                            {
+                                cvrep.rmserror = cvrep.rmserror+math.sqr(y[j]-1);
+                                cvrep.avgerror = cvrep.avgerror+Math.Abs(y[j]-1);
+                                cvrep.avgrelerror = cvrep.avgrelerror+Math.Abs(y[j]-1);
+                                relcnt = relcnt+1;
+                            }
+                            else
+                            {
+                                cvrep.rmserror = cvrep.rmserror+math.sqr(y[j]);
+                                cvrep.avgerror = cvrep.avgerror+Math.Abs(y[j]);
+                            }
+                        }
+                    }
+                    else
+                    {
+                        
+                        //
+                        // Regression-specific code
+                        //
+                        for(j=0; j<=nout-1; j++)
+                        {
+                            cvrep.rmserror = cvrep.rmserror+math.sqr(y[j]-testset[i,nin+j]);
+                            cvrep.avgerror = cvrep.avgerror+Math.Abs(y[j]-testset[i,nin+j]);
+                            if( (double)(testset[i,nin+j])!=(double)(0) )
+                            {
+                                cvrep.avgrelerror = cvrep.avgrelerror+Math.Abs((y[j]-testset[i,nin+j])/testset[i,nin+j]);
+                                relcnt = relcnt+1;
+                            }
+                        }
+                    }
+                }
+            }
+            if( mlpbase.mlpissoftmax(network) )
+            {
+                cvrep.relclserror = cvrep.relclserror/npoints;
+                cvrep.avgce = cvrep.avgce/(Math.Log(2)*npoints);
+            }
+            cvrep.rmserror = Math.Sqrt(cvrep.rmserror/(npoints*nout));
+            cvrep.avgerror = cvrep.avgerror/(npoints*nout);
+            cvrep.avgrelerror = cvrep.avgrelerror/relcnt;
+            info = 1;
+        }
+
+
+        /*************************************************************************
+        Subroutine prepares K-fold split of the training set.
+
+        NOTES:
+            "NClasses>0" means that we have classification task.
+            "NClasses<0" means regression task with -NClasses real outputs.
+        *************************************************************************/
+        private static void mlpkfoldsplit(double[,] xy,
+            int npoints,
+            int nclasses,
+            int foldscount,
+            bool stratifiedsplits,
+            ref int[] folds)
+        {
+            int i = 0;
+            int j = 0;
+            int k = 0;
+
+            folds = new int[0];
+
+            
+            //
+            // test parameters
+            //
+            alglib.ap.assert(npoints>0, "MLPKFoldSplit: wrong NPoints!");
+            alglib.ap.assert(nclasses>1 || nclasses<0, "MLPKFoldSplit: wrong NClasses!");
+            alglib.ap.assert(foldscount>=2 && foldscount<=npoints, "MLPKFoldSplit: wrong FoldsCount!");
+            alglib.ap.assert(!stratifiedsplits, "MLPKFoldSplit: stratified splits are not supported!");
+            
+            //
+            // Folds
+            //
+            folds = new int[npoints-1+1];
+            for(i=0; i<=npoints-1; i++)
+            {
+                folds[i] = i*foldscount/npoints;
+            }
+            for(i=0; i<=npoints-2; i++)
+            {
+                j = i+math.randominteger(npoints-i);
+                if( j!=i )
+                {
+                    k = folds[i];
+                    folds[i] = folds[j];
+                    folds[j] = k;
+                }
+            }
+        }
+
+
+    }
+    public class mlpe
+    {
+        /*************************************************************************
+        Neural networks ensemble
+        *************************************************************************/
+        public class mlpensemble
+        {
+            public int ensemblesize;
+            public double[] weights;
+            public double[] columnmeans;
+            public double[] columnsigmas;
+            public mlpbase.multilayerperceptron network;
+            public double[] y;
+            public mlpensemble()
+            {
+                weights = new double[0];
+                columnmeans = new double[0];
+                columnsigmas = new double[0];
+                network = new mlpbase.multilayerperceptron();
+                y = new double[0];
+            }
+        };
+
+
+
+
+        public const int mlpntotaloffset = 3;
+        public const int mlpevnum = 9;
+        public const int mlpefirstversion = 1;
+
+
+        /*************************************************************************
+        Like MLPCreate0, but for ensembles.
+
+          -- ALGLIB --
+             Copyright 18.02.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpecreate0(int nin,
+            int nout,
+            int ensemblesize,
+            mlpensemble ensemble)
+        {
+            mlpbase.multilayerperceptron net = new mlpbase.multilayerperceptron();
+
+            mlpbase.mlpcreate0(nin, nout, net);
+            mlpecreatefromnetwork(net, ensemblesize, ensemble);
+        }
+
+
+        /*************************************************************************
+        Like MLPCreate1, but for ensembles.
+
+          -- ALGLIB --
+             Copyright 18.02.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpecreate1(int nin,
+            int nhid,
+            int nout,
+            int ensemblesize,
+            mlpensemble ensemble)
+        {
+            mlpbase.multilayerperceptron net = new mlpbase.multilayerperceptron();
+
+            mlpbase.mlpcreate1(nin, nhid, nout, net);
+            mlpecreatefromnetwork(net, ensemblesize, ensemble);
+        }
+
+
+        /*************************************************************************
+        Like MLPCreate2, but for ensembles.
+
+          -- ALGLIB --
+             Copyright 18.02.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpecreate2(int nin,
+            int nhid1,
+            int nhid2,
+            int nout,
+            int ensemblesize,
+            mlpensemble ensemble)
+        {
+            mlpbase.multilayerperceptron net = new mlpbase.multilayerperceptron();
+
+            mlpbase.mlpcreate2(nin, nhid1, nhid2, nout, net);
+            mlpecreatefromnetwork(net, ensemblesize, ensemble);
+        }
+
+
+        /*************************************************************************
+        Like MLPCreateB0, but for ensembles.
+
+          -- ALGLIB --
+             Copyright 18.02.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpecreateb0(int nin,
+            int nout,
+            double b,
+            double d,
+            int ensemblesize,
+            mlpensemble ensemble)
+        {
+            mlpbase.multilayerperceptron net = new mlpbase.multilayerperceptron();
+
+            mlpbase.mlpcreateb0(nin, nout, b, d, net);
+            mlpecreatefromnetwork(net, ensemblesize, ensemble);
+        }
+
+
+        /*************************************************************************
+        Like MLPCreateB1, but for ensembles.
+
+          -- ALGLIB --
+             Copyright 18.02.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpecreateb1(int nin,
+            int nhid,
+            int nout,
+            double b,
+            double d,
+            int ensemblesize,
+            mlpensemble ensemble)
+        {
+            mlpbase.multilayerperceptron net = new mlpbase.multilayerperceptron();
+
+            mlpbase.mlpcreateb1(nin, nhid, nout, b, d, net);
+            mlpecreatefromnetwork(net, ensemblesize, ensemble);
+        }
+
+
+        /*************************************************************************
+        Like MLPCreateB2, but for ensembles.
+
+          -- ALGLIB --
+             Copyright 18.02.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpecreateb2(int nin,
+            int nhid1,
+            int nhid2,
+            int nout,
+            double b,
+            double d,
+            int ensemblesize,
+            mlpensemble ensemble)
+        {
+            mlpbase.multilayerperceptron net = new mlpbase.multilayerperceptron();
+
+            mlpbase.mlpcreateb2(nin, nhid1, nhid2, nout, b, d, net);
+            mlpecreatefromnetwork(net, ensemblesize, ensemble);
+        }
+
+
+        /*************************************************************************
+        Like MLPCreateR0, but for ensembles.
+
+          -- ALGLIB --
+             Copyright 18.02.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpecreater0(int nin,
+            int nout,
+            double a,
+            double b,
+            int ensemblesize,
+            mlpensemble ensemble)
+        {
+            mlpbase.multilayerperceptron net = new mlpbase.multilayerperceptron();
+
+            mlpbase.mlpcreater0(nin, nout, a, b, net);
+            mlpecreatefromnetwork(net, ensemblesize, ensemble);
+        }
+
+
+        /*************************************************************************
+        Like MLPCreateR1, but for ensembles.
+
+          -- ALGLIB --
+             Copyright 18.02.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpecreater1(int nin,
+            int nhid,
+            int nout,
+            double a,
+            double b,
+            int ensemblesize,
+            mlpensemble ensemble)
+        {
+            mlpbase.multilayerperceptron net = new mlpbase.multilayerperceptron();
+
+            mlpbase.mlpcreater1(nin, nhid, nout, a, b, net);
+            mlpecreatefromnetwork(net, ensemblesize, ensemble);
+        }
+
+
+        /*************************************************************************
+        Like MLPCreateR2, but for ensembles.
+
+          -- ALGLIB --
+             Copyright 18.02.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpecreater2(int nin,
+            int nhid1,
+            int nhid2,
+            int nout,
+            double a,
+            double b,
+            int ensemblesize,
+            mlpensemble ensemble)
+        {
+            mlpbase.multilayerperceptron net = new mlpbase.multilayerperceptron();
+
+            mlpbase.mlpcreater2(nin, nhid1, nhid2, nout, a, b, net);
+            mlpecreatefromnetwork(net, ensemblesize, ensemble);
+        }
+
+
+        /*************************************************************************
+        Like MLPCreateC0, but for ensembles.
+
+          -- ALGLIB --
+             Copyright 18.02.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpecreatec0(int nin,
+            int nout,
+            int ensemblesize,
+            mlpensemble ensemble)
+        {
+            mlpbase.multilayerperceptron net = new mlpbase.multilayerperceptron();
+
+            mlpbase.mlpcreatec0(nin, nout, net);
+            mlpecreatefromnetwork(net, ensemblesize, ensemble);
+        }
+
+
+        /*************************************************************************
+        Like MLPCreateC1, but for ensembles.
+
+          -- ALGLIB --
+             Copyright 18.02.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpecreatec1(int nin,
+            int nhid,
+            int nout,
+            int ensemblesize,
+            mlpensemble ensemble)
+        {
+            mlpbase.multilayerperceptron net = new mlpbase.multilayerperceptron();
+
+            mlpbase.mlpcreatec1(nin, nhid, nout, net);
+            mlpecreatefromnetwork(net, ensemblesize, ensemble);
+        }
+
+
+        /*************************************************************************
+        Like MLPCreateC2, but for ensembles.
+
+          -- ALGLIB --
+             Copyright 18.02.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpecreatec2(int nin,
+            int nhid1,
+            int nhid2,
+            int nout,
+            int ensemblesize,
+            mlpensemble ensemble)
+        {
+            mlpbase.multilayerperceptron net = new mlpbase.multilayerperceptron();
+
+            mlpbase.mlpcreatec2(nin, nhid1, nhid2, nout, net);
+            mlpecreatefromnetwork(net, ensemblesize, ensemble);
+        }
+
+
+        /*************************************************************************
+        Creates ensemble from network. Only network geometry is copied.
+
+          -- ALGLIB --
+             Copyright 17.02.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpecreatefromnetwork(mlpbase.multilayerperceptron network,
+            int ensemblesize,
+            mlpensemble ensemble)
+        {
+            int i = 0;
+            int ccount = 0;
+            int wcount = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            alglib.ap.assert(ensemblesize>0, "MLPECreate: incorrect ensemble size!");
+            
+            //
+            // Copy network
+            //
+            mlpbase.mlpcopy(network, ensemble.network);
+            
+            //
+            // network properties
+            //
+            if( mlpbase.mlpissoftmax(network) )
+            {
+                ccount = mlpbase.mlpgetinputscount(ensemble.network);
+            }
+            else
+            {
+                ccount = mlpbase.mlpgetinputscount(ensemble.network)+mlpbase.mlpgetoutputscount(ensemble.network);
+            }
+            wcount = mlpbase.mlpgetweightscount(ensemble.network);
+            ensemble.ensemblesize = ensemblesize;
+            
+            //
+            // weights, means, sigmas
+            //
+            ensemble.weights = new double[ensemblesize*wcount];
+            ensemble.columnmeans = new double[ensemblesize*ccount];
+            ensemble.columnsigmas = new double[ensemblesize*ccount];
+            for(i=0; i<=ensemblesize*wcount-1; i++)
+            {
+                ensemble.weights[i] = math.randomreal()-0.5;
+            }
+            for(i=0; i<=ensemblesize-1; i++)
+            {
+                i1_ = (0) - (i*ccount);
+                for(i_=i*ccount; i_<=(i+1)*ccount-1;i_++)
+                {
+                    ensemble.columnmeans[i_] = network.columnmeans[i_+i1_];
+                }
+                i1_ = (0) - (i*ccount);
+                for(i_=i*ccount; i_<=(i+1)*ccount-1;i_++)
+                {
+                    ensemble.columnsigmas[i_] = network.columnsigmas[i_+i1_];
+                }
+            }
+            
+            //
+            // temporaries, internal buffers
+            //
+            ensemble.y = new double[mlpbase.mlpgetoutputscount(ensemble.network)];
+        }
+
+
+        /*************************************************************************
+        Copying of MLPEnsemble strucure
+
+        INPUT PARAMETERS:
+            Ensemble1 -   original
+
+        OUTPUT PARAMETERS:
+            Ensemble2 -   copy
+
+          -- ALGLIB --
+             Copyright 17.02.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpecopy(mlpensemble ensemble1,
+            mlpensemble ensemble2)
+        {
+            int i = 0;
+            int ccount = 0;
+            int wcount = 0;
+            int i_ = 0;
+
+            
+            //
+            // Unload info
+            //
+            if( mlpbase.mlpissoftmax(ensemble1.network) )
+            {
+                ccount = mlpbase.mlpgetinputscount(ensemble1.network);
+            }
+            else
+            {
+                ccount = mlpbase.mlpgetinputscount(ensemble1.network)+mlpbase.mlpgetoutputscount(ensemble1.network);
+            }
+            wcount = mlpbase.mlpgetweightscount(ensemble1.network);
+            
+            //
+            // Allocate space
+            //
+            ensemble2.weights = new double[ensemble1.ensemblesize*wcount];
+            ensemble2.columnmeans = new double[ensemble1.ensemblesize*ccount];
+            ensemble2.columnsigmas = new double[ensemble1.ensemblesize*ccount];
+            ensemble2.y = new double[mlpbase.mlpgetoutputscount(ensemble1.network)];
+            
+            //
+            // Copy
+            //
+            ensemble2.ensemblesize = ensemble1.ensemblesize;
+            for(i_=0; i_<=ensemble1.ensemblesize*wcount-1;i_++)
+            {
+                ensemble2.weights[i_] = ensemble1.weights[i_];
+            }
+            for(i_=0; i_<=ensemble1.ensemblesize*ccount-1;i_++)
+            {
+                ensemble2.columnmeans[i_] = ensemble1.columnmeans[i_];
+            }
+            for(i_=0; i_<=ensemble1.ensemblesize*ccount-1;i_++)
+            {
+                ensemble2.columnsigmas[i_] = ensemble1.columnsigmas[i_];
+            }
+            mlpbase.mlpcopy(ensemble1.network, ensemble2.network);
+        }
+
+
+        /*************************************************************************
+        Randomization of MLP ensemble
+
+          -- ALGLIB --
+             Copyright 17.02.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlperandomize(mlpensemble ensemble)
+        {
+            int i = 0;
+            int wcount = 0;
+
+            wcount = mlpbase.mlpgetweightscount(ensemble.network);
+            for(i=0; i<=ensemble.ensemblesize*wcount-1; i++)
+            {
+                ensemble.weights[i] = math.randomreal()-0.5;
+            }
+        }
+
+
+        /*************************************************************************
+        Return ensemble properties (number of inputs and outputs).
+
+          -- ALGLIB --
+             Copyright 17.02.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpeproperties(mlpensemble ensemble,
+            ref int nin,
+            ref int nout)
+        {
+            nin = 0;
+            nout = 0;
+
+            nin = mlpbase.mlpgetinputscount(ensemble.network);
+            nout = mlpbase.mlpgetoutputscount(ensemble.network);
+        }
+
+
+        /*************************************************************************
+        Return normalization type (whether ensemble is SOFTMAX-normalized or not).
+
+          -- ALGLIB --
+             Copyright 17.02.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static bool mlpeissoftmax(mlpensemble ensemble)
+        {
+            bool result = new bool();
+
+            result = mlpbase.mlpissoftmax(ensemble.network);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Procesing
+
+        INPUT PARAMETERS:
+            Ensemble-   neural networks ensemble
+            X       -   input vector,  array[0..NIn-1].
+            Y       -   (possibly) preallocated buffer; if size of Y is less than
+                        NOut, it will be reallocated. If it is large enough, it
+                        is NOT reallocated, so we can save some time on reallocation.
+
+
+        OUTPUT PARAMETERS:
+            Y       -   result. Regression estimate when solving regression  task,
+                        vector of posterior probabilities for classification task.
+
+          -- ALGLIB --
+             Copyright 17.02.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpeprocess(mlpensemble ensemble,
+            double[] x,
+            ref double[] y)
+        {
+            int i = 0;
+            int es = 0;
+            int wc = 0;
+            int cc = 0;
+            double v = 0;
+            int nout = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            if( alglib.ap.len(y)<mlpbase.mlpgetoutputscount(ensemble.network) )
+            {
+                y = new double[mlpbase.mlpgetoutputscount(ensemble.network)];
+            }
+            es = ensemble.ensemblesize;
+            wc = mlpbase.mlpgetweightscount(ensemble.network);
+            if( mlpbase.mlpissoftmax(ensemble.network) )
+            {
+                cc = mlpbase.mlpgetinputscount(ensemble.network);
+            }
+            else
+            {
+                cc = mlpbase.mlpgetinputscount(ensemble.network)+mlpbase.mlpgetoutputscount(ensemble.network);
+            }
+            v = (double)1/(double)es;
+            nout = mlpbase.mlpgetoutputscount(ensemble.network);
+            for(i=0; i<=nout-1; i++)
+            {
+                y[i] = 0;
+            }
+            for(i=0; i<=es-1; i++)
+            {
+                i1_ = (i*wc) - (0);
+                for(i_=0; i_<=wc-1;i_++)
+                {
+                    ensemble.network.weights[i_] = ensemble.weights[i_+i1_];
+                }
+                i1_ = (i*cc) - (0);
+                for(i_=0; i_<=cc-1;i_++)
+                {
+                    ensemble.network.columnmeans[i_] = ensemble.columnmeans[i_+i1_];
+                }
+                i1_ = (i*cc) - (0);
+                for(i_=0; i_<=cc-1;i_++)
+                {
+                    ensemble.network.columnsigmas[i_] = ensemble.columnsigmas[i_+i1_];
+                }
+                mlpbase.mlpprocess(ensemble.network, x, ref ensemble.y);
+                for(i_=0; i_<=nout-1;i_++)
+                {
+                    y[i_] = y[i_] + v*ensemble.y[i_];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        'interactive'  variant  of  MLPEProcess  for  languages  like Python which
+        support constructs like "Y = MLPEProcess(LM,X)" and interactive mode of the
+        interpreter
+
+        This function allocates new array on each call,  so  it  is  significantly
+        slower than its 'non-interactive' counterpart, but it is  more  convenient
+        when you call it from command line.
+
+          -- ALGLIB --
+             Copyright 17.02.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpeprocessi(mlpensemble ensemble,
+            double[] x,
+            ref double[] y)
+        {
+            y = new double[0];
+
+            mlpeprocess(ensemble, x, ref y);
+        }
+
+
+        /*************************************************************************
+        Relative classification error on the test set
+
+        INPUT PARAMETERS:
+            Ensemble-   ensemble
+            XY      -   test set
+            NPoints -   test set size
+
+        RESULT:
+            percent of incorrectly classified cases.
+            Works both for classifier betwork and for regression networks which
+        are used as classifiers.
+
+          -- ALGLIB --
+             Copyright 17.02.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static double mlperelclserror(mlpensemble ensemble,
+            double[,] xy,
+            int npoints)
+        {
+            double result = 0;
+            double relcls = 0;
+            double avgce = 0;
+            double rms = 0;
+            double avg = 0;
+            double avgrel = 0;
+
+            mlpeallerrors(ensemble, xy, npoints, ref relcls, ref avgce, ref rms, ref avg, ref avgrel);
+            result = relcls;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Average cross-entropy (in bits per element) on the test set
+
+        INPUT PARAMETERS:
+            Ensemble-   ensemble
+            XY      -   test set
+            NPoints -   test set size
+
+        RESULT:
+            CrossEntropy/(NPoints*LN(2)).
+            Zero if ensemble solves regression task.
+
+          -- ALGLIB --
+             Copyright 17.02.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static double mlpeavgce(mlpensemble ensemble,
+            double[,] xy,
+            int npoints)
+        {
+            double result = 0;
+            double relcls = 0;
+            double avgce = 0;
+            double rms = 0;
+            double avg = 0;
+            double avgrel = 0;
+
+            mlpeallerrors(ensemble, xy, npoints, ref relcls, ref avgce, ref rms, ref avg, ref avgrel);
+            result = avgce;
+            return result;
+        }
+
+
+        /*************************************************************************
+        RMS error on the test set
+
+        INPUT PARAMETERS:
+            Ensemble-   ensemble
+            XY      -   test set
+            NPoints -   test set size
+
+        RESULT:
+            root mean square error.
+            Its meaning for regression task is obvious. As for classification task
+        RMS error means error when estimating posterior probabilities.
+
+          -- ALGLIB --
+             Copyright 17.02.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static double mlpermserror(mlpensemble ensemble,
+            double[,] xy,
+            int npoints)
+        {
+            double result = 0;
+            double relcls = 0;
+            double avgce = 0;
+            double rms = 0;
+            double avg = 0;
+            double avgrel = 0;
+
+            mlpeallerrors(ensemble, xy, npoints, ref relcls, ref avgce, ref rms, ref avg, ref avgrel);
+            result = rms;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Average error on the test set
+
+        INPUT PARAMETERS:
+            Ensemble-   ensemble
+            XY      -   test set
+            NPoints -   test set size
+
+        RESULT:
+            Its meaning for regression task is obvious. As for classification task
+        it means average error when estimating posterior probabilities.
+
+          -- ALGLIB --
+             Copyright 17.02.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static double mlpeavgerror(mlpensemble ensemble,
+            double[,] xy,
+            int npoints)
+        {
+            double result = 0;
+            double relcls = 0;
+            double avgce = 0;
+            double rms = 0;
+            double avg = 0;
+            double avgrel = 0;
+
+            mlpeallerrors(ensemble, xy, npoints, ref relcls, ref avgce, ref rms, ref avg, ref avgrel);
+            result = avg;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Average relative error on the test set
+
+        INPUT PARAMETERS:
+            Ensemble-   ensemble
+            XY      -   test set
+            NPoints -   test set size
+
+        RESULT:
+            Its meaning for regression task is obvious. As for classification task
+        it means average relative error when estimating posterior probabilities.
+
+          -- ALGLIB --
+             Copyright 17.02.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static double mlpeavgrelerror(mlpensemble ensemble,
+            double[,] xy,
+            int npoints)
+        {
+            double result = 0;
+            double relcls = 0;
+            double avgce = 0;
+            double rms = 0;
+            double avg = 0;
+            double avgrel = 0;
+
+            mlpeallerrors(ensemble, xy, npoints, ref relcls, ref avgce, ref rms, ref avg, ref avgrel);
+            result = avgrel;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Training neural networks ensemble using  bootstrap  aggregating (bagging).
+        Modified Levenberg-Marquardt algorithm is used as base training method.
+
+        INPUT PARAMETERS:
+            Ensemble    -   model with initialized geometry
+            XY          -   training set
+            NPoints     -   training set size
+            Decay       -   weight decay coefficient, >=0.001
+            Restarts    -   restarts, >0.
+
+        OUTPUT PARAMETERS:
+            Ensemble    -   trained model
+            Info        -   return code:
+                            * -2, if there is a point with class number
+                                  outside of [0..NClasses-1].
+                            * -1, if incorrect parameters was passed
+                                  (NPoints<0, Restarts<1).
+                            *  2, if task has been solved.
+            Rep         -   training report.
+            OOBErrors   -   out-of-bag generalization error estimate
+
+          -- ALGLIB --
+             Copyright 17.02.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpebagginglm(mlpensemble ensemble,
+            double[,] xy,
+            int npoints,
+            double decay,
+            int restarts,
+            ref int info,
+            mlptrain.mlpreport rep,
+            mlptrain.mlpcvreport ooberrors)
+        {
+            info = 0;
+
+            mlpebagginginternal(ensemble, xy, npoints, decay, restarts, 0.0, 0, true, ref info, rep, ooberrors);
+        }
+
+
+        /*************************************************************************
+        Training neural networks ensemble using  bootstrap  aggregating (bagging).
+        L-BFGS algorithm is used as base training method.
+
+        INPUT PARAMETERS:
+            Ensemble    -   model with initialized geometry
+            XY          -   training set
+            NPoints     -   training set size
+            Decay       -   weight decay coefficient, >=0.001
+            Restarts    -   restarts, >0.
+            WStep       -   stopping criterion, same as in MLPTrainLBFGS
+            MaxIts      -   stopping criterion, same as in MLPTrainLBFGS
+
+        OUTPUT PARAMETERS:
+            Ensemble    -   trained model
+            Info        -   return code:
+                            * -8, if both WStep=0 and MaxIts=0
+                            * -2, if there is a point with class number
+                                  outside of [0..NClasses-1].
+                            * -1, if incorrect parameters was passed
+                                  (NPoints<0, Restarts<1).
+                            *  2, if task has been solved.
+            Rep         -   training report.
+            OOBErrors   -   out-of-bag generalization error estimate
+
+          -- ALGLIB --
+             Copyright 17.02.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpebagginglbfgs(mlpensemble ensemble,
+            double[,] xy,
+            int npoints,
+            double decay,
+            int restarts,
+            double wstep,
+            int maxits,
+            ref int info,
+            mlptrain.mlpreport rep,
+            mlptrain.mlpcvreport ooberrors)
+        {
+            info = 0;
+
+            mlpebagginginternal(ensemble, xy, npoints, decay, restarts, wstep, maxits, false, ref info, rep, ooberrors);
+        }
+
+
+        /*************************************************************************
+        Training neural networks ensemble using early stopping.
+
+        INPUT PARAMETERS:
+            Ensemble    -   model with initialized geometry
+            XY          -   training set
+            NPoints     -   training set size
+            Decay       -   weight decay coefficient, >=0.001
+            Restarts    -   restarts, >0.
+
+        OUTPUT PARAMETERS:
+            Ensemble    -   trained model
+            Info        -   return code:
+                            * -2, if there is a point with class number
+                                  outside of [0..NClasses-1].
+                            * -1, if incorrect parameters was passed
+                                  (NPoints<0, Restarts<1).
+                            *  6, if task has been solved.
+            Rep         -   training report.
+            OOBErrors   -   out-of-bag generalization error estimate
+
+          -- ALGLIB --
+             Copyright 10.03.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpetraines(mlpensemble ensemble,
+            double[,] xy,
+            int npoints,
+            double decay,
+            int restarts,
+            ref int info,
+            mlptrain.mlpreport rep)
+        {
+            int i = 0;
+            int k = 0;
+            int ccount = 0;
+            int pcount = 0;
+            double[,] trnxy = new double[0,0];
+            double[,] valxy = new double[0,0];
+            int trnsize = 0;
+            int valsize = 0;
+            int tmpinfo = 0;
+            mlptrain.mlpreport tmprep = new mlptrain.mlpreport();
+            int nin = 0;
+            int nout = 0;
+            int wcount = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            info = 0;
+
+            nin = mlpbase.mlpgetinputscount(ensemble.network);
+            nout = mlpbase.mlpgetoutputscount(ensemble.network);
+            wcount = mlpbase.mlpgetweightscount(ensemble.network);
+            if( (npoints<2 || restarts<1) || (double)(decay)<(double)(0) )
+            {
+                info = -1;
+                return;
+            }
+            if( mlpbase.mlpissoftmax(ensemble.network) )
+            {
+                for(i=0; i<=npoints-1; i++)
+                {
+                    if( (int)Math.Round(xy[i,nin])<0 || (int)Math.Round(xy[i,nin])>=nout )
+                    {
+                        info = -2;
+                        return;
+                    }
+                }
+            }
+            info = 6;
+            
+            //
+            // allocate
+            //
+            if( mlpbase.mlpissoftmax(ensemble.network) )
+            {
+                ccount = nin+1;
+                pcount = nin;
+            }
+            else
+            {
+                ccount = nin+nout;
+                pcount = nin+nout;
+            }
+            trnxy = new double[npoints, ccount];
+            valxy = new double[npoints, ccount];
+            rep.ngrad = 0;
+            rep.nhess = 0;
+            rep.ncholesky = 0;
+            
+            //
+            // train networks
+            //
+            for(k=0; k<=ensemble.ensemblesize-1; k++)
+            {
+                
+                //
+                // Split set
+                //
+                do
+                {
+                    trnsize = 0;
+                    valsize = 0;
+                    for(i=0; i<=npoints-1; i++)
+                    {
+                        if( (double)(math.randomreal())<(double)(0.66) )
+                        {
+                            
+                            //
+                            // Assign sample to training set
+                            //
+                            for(i_=0; i_<=ccount-1;i_++)
+                            {
+                                trnxy[trnsize,i_] = xy[i,i_];
+                            }
+                            trnsize = trnsize+1;
+                        }
+                        else
+                        {
+                            
+                            //
+                            // Assign sample to validation set
+                            //
+                            for(i_=0; i_<=ccount-1;i_++)
+                            {
+                                valxy[valsize,i_] = xy[i,i_];
+                            }
+                            valsize = valsize+1;
+                        }
+                    }
+                }
+                while( !(trnsize!=0 && valsize!=0) );
+                
+                //
+                // Train
+                //
+                mlptrain.mlptraines(ensemble.network, trnxy, trnsize, valxy, valsize, decay, restarts, ref tmpinfo, tmprep);
+                if( tmpinfo<0 )
+                {
+                    info = tmpinfo;
+                    return;
+                }
+                
+                //
+                // save results
+                //
+                i1_ = (0) - (k*wcount);
+                for(i_=k*wcount; i_<=(k+1)*wcount-1;i_++)
+                {
+                    ensemble.weights[i_] = ensemble.network.weights[i_+i1_];
+                }
+                i1_ = (0) - (k*pcount);
+                for(i_=k*pcount; i_<=(k+1)*pcount-1;i_++)
+                {
+                    ensemble.columnmeans[i_] = ensemble.network.columnmeans[i_+i1_];
+                }
+                i1_ = (0) - (k*pcount);
+                for(i_=k*pcount; i_<=(k+1)*pcount-1;i_++)
+                {
+                    ensemble.columnsigmas[i_] = ensemble.network.columnsigmas[i_+i1_];
+                }
+                rep.ngrad = rep.ngrad+tmprep.ngrad;
+                rep.nhess = rep.nhess+tmprep.nhess;
+                rep.ncholesky = rep.ncholesky+tmprep.ncholesky;
+            }
+        }
+
+
+        /*************************************************************************
+        Serializer: allocation
+
+          -- ALGLIB --
+             Copyright 19.10.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpealloc(alglib.serializer s,
+            mlpensemble ensemble)
+        {
+            s.alloc_entry();
+            s.alloc_entry();
+            s.alloc_entry();
+            apserv.allocrealarray(s, ensemble.weights, -1);
+            apserv.allocrealarray(s, ensemble.columnmeans, -1);
+            apserv.allocrealarray(s, ensemble.columnsigmas, -1);
+            mlpbase.mlpalloc(s, ensemble.network);
+        }
+
+
+        /*************************************************************************
+        Serializer: serialization
+
+          -- ALGLIB --
+             Copyright 14.03.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpeserialize(alglib.serializer s,
+            mlpensemble ensemble)
+        {
+            s.serialize_int(scodes.getmlpeserializationcode());
+            s.serialize_int(mlpefirstversion);
+            s.serialize_int(ensemble.ensemblesize);
+            apserv.serializerealarray(s, ensemble.weights, -1);
+            apserv.serializerealarray(s, ensemble.columnmeans, -1);
+            apserv.serializerealarray(s, ensemble.columnsigmas, -1);
+            mlpbase.mlpserialize(s, ensemble.network);
+        }
+
+
+        /*************************************************************************
+        Serializer: unserialization
+
+          -- ALGLIB --
+             Copyright 14.03.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpeunserialize(alglib.serializer s,
+            mlpensemble ensemble)
+        {
+            int i0 = 0;
+            int i1 = 0;
+
+            
+            //
+            // check correctness of header
+            //
+            i0 = s.unserialize_int();
+            alglib.ap.assert(i0==scodes.getmlpeserializationcode(), "MLPEUnserialize: stream header corrupted");
+            i1 = s.unserialize_int();
+            alglib.ap.assert(i1==mlpefirstversion, "MLPEUnserialize: stream header corrupted");
+            
+            //
+            // Create network
+            //
+            ensemble.ensemblesize = s.unserialize_int();
+            apserv.unserializerealarray(s, ref ensemble.weights);
+            apserv.unserializerealarray(s, ref ensemble.columnmeans);
+            apserv.unserializerealarray(s, ref ensemble.columnsigmas);
+            mlpbase.mlpunserialize(s, ensemble.network);
+            
+            //
+            // Allocate termoraries
+            //
+            ensemble.y = new double[mlpbase.mlpgetoutputscount(ensemble.network)];
+        }
+
+
+        /*************************************************************************
+        Calculation of all types of errors
+
+          -- ALGLIB --
+             Copyright 17.02.2009 by Bochkanov Sergey
+        *************************************************************************/
+        private static void mlpeallerrors(mlpensemble ensemble,
+            double[,] xy,
+            int npoints,
+            ref double relcls,
+            ref double avgce,
+            ref double rms,
+            ref double avg,
+            ref double avgrel)
+        {
+            int i = 0;
+            double[] buf = new double[0];
+            double[] workx = new double[0];
+            double[] y = new double[0];
+            double[] dy = new double[0];
+            int nin = 0;
+            int nout = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            relcls = 0;
+            avgce = 0;
+            rms = 0;
+            avg = 0;
+            avgrel = 0;
+
+            nin = mlpbase.mlpgetinputscount(ensemble.network);
+            nout = mlpbase.mlpgetoutputscount(ensemble.network);
+            workx = new double[nin];
+            y = new double[nout];
+            if( mlpbase.mlpissoftmax(ensemble.network) )
+            {
+                dy = new double[1];
+                bdss.dserrallocate(nout, ref buf);
+            }
+            else
+            {
+                dy = new double[nout];
+                bdss.dserrallocate(-nout, ref buf);
+            }
+            for(i=0; i<=npoints-1; i++)
+            {
+                for(i_=0; i_<=nin-1;i_++)
+                {
+                    workx[i_] = xy[i,i_];
+                }
+                mlpeprocess(ensemble, workx, ref y);
+                if( mlpbase.mlpissoftmax(ensemble.network) )
+                {
+                    dy[0] = xy[i,nin];
+                }
+                else
+                {
+                    i1_ = (nin) - (0);
+                    for(i_=0; i_<=nout-1;i_++)
+                    {
+                        dy[i_] = xy[i,i_+i1_];
+                    }
+                }
+                bdss.dserraccumulate(ref buf, y, dy);
+            }
+            bdss.dserrfinish(ref buf);
+            relcls = buf[0];
+            avgce = buf[1];
+            rms = buf[2];
+            avg = buf[3];
+            avgrel = buf[4];
+        }
+
+
+        /*************************************************************************
+        Internal bagging subroutine.
+
+          -- ALGLIB --
+             Copyright 19.02.2009 by Bochkanov Sergey
+        *************************************************************************/
+        private static void mlpebagginginternal(mlpensemble ensemble,
+            double[,] xy,
+            int npoints,
+            double decay,
+            int restarts,
+            double wstep,
+            int maxits,
+            bool lmalgorithm,
+            ref int info,
+            mlptrain.mlpreport rep,
+            mlptrain.mlpcvreport ooberrors)
+        {
+            double[,] xys = new double[0,0];
+            bool[] s = new bool[0];
+            double[,] oobbuf = new double[0,0];
+            int[] oobcntbuf = new int[0];
+            double[] x = new double[0];
+            double[] y = new double[0];
+            double[] dy = new double[0];
+            double[] dsbuf = new double[0];
+            int ccnt = 0;
+            int pcnt = 0;
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            double v = 0;
+            mlptrain.mlpreport tmprep = new mlptrain.mlpreport();
+            int nin = 0;
+            int nout = 0;
+            int wcount = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            info = 0;
+
+            nin = mlpbase.mlpgetinputscount(ensemble.network);
+            nout = mlpbase.mlpgetoutputscount(ensemble.network);
+            wcount = mlpbase.mlpgetweightscount(ensemble.network);
+            
+            //
+            // Test for inputs
+            //
+            if( (!lmalgorithm && (double)(wstep)==(double)(0)) && maxits==0 )
+            {
+                info = -8;
+                return;
+            }
+            if( ((npoints<=0 || restarts<1) || (double)(wstep)<(double)(0)) || maxits<0 )
+            {
+                info = -1;
+                return;
+            }
+            if( mlpbase.mlpissoftmax(ensemble.network) )
+            {
+                for(i=0; i<=npoints-1; i++)
+                {
+                    if( (int)Math.Round(xy[i,nin])<0 || (int)Math.Round(xy[i,nin])>=nout )
+                    {
+                        info = -2;
+                        return;
+                    }
+                }
+            }
+            
+            //
+            // allocate temporaries
+            //
+            info = 2;
+            rep.ngrad = 0;
+            rep.nhess = 0;
+            rep.ncholesky = 0;
+            ooberrors.relclserror = 0;
+            ooberrors.avgce = 0;
+            ooberrors.rmserror = 0;
+            ooberrors.avgerror = 0;
+            ooberrors.avgrelerror = 0;
+            if( mlpbase.mlpissoftmax(ensemble.network) )
+            {
+                ccnt = nin+1;
+                pcnt = nin;
+            }
+            else
+            {
+                ccnt = nin+nout;
+                pcnt = nin+nout;
+            }
+            xys = new double[npoints, ccnt];
+            s = new bool[npoints];
+            oobbuf = new double[npoints, nout];
+            oobcntbuf = new int[npoints];
+            x = new double[nin];
+            y = new double[nout];
+            if( mlpbase.mlpissoftmax(ensemble.network) )
+            {
+                dy = new double[1];
+            }
+            else
+            {
+                dy = new double[nout];
+            }
+            for(i=0; i<=npoints-1; i++)
+            {
+                for(j=0; j<=nout-1; j++)
+                {
+                    oobbuf[i,j] = 0;
+                }
+            }
+            for(i=0; i<=npoints-1; i++)
+            {
+                oobcntbuf[i] = 0;
+            }
+            
+            //
+            // main bagging cycle
+            //
+            for(k=0; k<=ensemble.ensemblesize-1; k++)
+            {
+                
+                //
+                // prepare dataset
+                //
+                for(i=0; i<=npoints-1; i++)
+                {
+                    s[i] = false;
+                }
+                for(i=0; i<=npoints-1; i++)
+                {
+                    j = math.randominteger(npoints);
+                    s[j] = true;
+                    for(i_=0; i_<=ccnt-1;i_++)
+                    {
+                        xys[i,i_] = xy[j,i_];
+                    }
+                }
+                
+                //
+                // train
+                //
+                if( lmalgorithm )
+                {
+                    mlptrain.mlptrainlm(ensemble.network, xys, npoints, decay, restarts, ref info, tmprep);
+                }
+                else
+                {
+                    mlptrain.mlptrainlbfgs(ensemble.network, xys, npoints, decay, restarts, wstep, maxits, ref info, tmprep);
+                }
+                if( info<0 )
+                {
+                    return;
+                }
+                
+                //
+                // save results
+                //
+                rep.ngrad = rep.ngrad+tmprep.ngrad;
+                rep.nhess = rep.nhess+tmprep.nhess;
+                rep.ncholesky = rep.ncholesky+tmprep.ncholesky;
+                i1_ = (0) - (k*wcount);
+                for(i_=k*wcount; i_<=(k+1)*wcount-1;i_++)
+                {
+                    ensemble.weights[i_] = ensemble.network.weights[i_+i1_];
+                }
+                i1_ = (0) - (k*pcnt);
+                for(i_=k*pcnt; i_<=(k+1)*pcnt-1;i_++)
+                {
+                    ensemble.columnmeans[i_] = ensemble.network.columnmeans[i_+i1_];
+                }
+                i1_ = (0) - (k*pcnt);
+                for(i_=k*pcnt; i_<=(k+1)*pcnt-1;i_++)
+                {
+                    ensemble.columnsigmas[i_] = ensemble.network.columnsigmas[i_+i1_];
+                }
+                
+                //
+                // OOB estimates
+                //
+                for(i=0; i<=npoints-1; i++)
+                {
+                    if( !s[i] )
+                    {
+                        for(i_=0; i_<=nin-1;i_++)
+                        {
+                            x[i_] = xy[i,i_];
+                        }
+                        mlpbase.mlpprocess(ensemble.network, x, ref y);
+                        for(i_=0; i_<=nout-1;i_++)
+                        {
+                            oobbuf[i,i_] = oobbuf[i,i_] + y[i_];
+                        }
+                        oobcntbuf[i] = oobcntbuf[i]+1;
+                    }
+                }
+            }
+            
+            //
+            // OOB estimates
+            //
+            if( mlpbase.mlpissoftmax(ensemble.network) )
+            {
+                bdss.dserrallocate(nout, ref dsbuf);
+            }
+            else
+            {
+                bdss.dserrallocate(-nout, ref dsbuf);
+            }
+            for(i=0; i<=npoints-1; i++)
+            {
+                if( oobcntbuf[i]!=0 )
+                {
+                    v = (double)1/(double)oobcntbuf[i];
+                    for(i_=0; i_<=nout-1;i_++)
+                    {
+                        y[i_] = v*oobbuf[i,i_];
+                    }
+                    if( mlpbase.mlpissoftmax(ensemble.network) )
+                    {
+                        dy[0] = xy[i,nin];
+                    }
+                    else
+                    {
+                        i1_ = (nin) - (0);
+                        for(i_=0; i_<=nout-1;i_++)
+                        {
+                            dy[i_] = v*xy[i,i_+i1_];
+                        }
+                    }
+                    bdss.dserraccumulate(ref dsbuf, y, dy);
+                }
+            }
+            bdss.dserrfinish(ref dsbuf);
+            ooberrors.relclserror = dsbuf[0];
+            ooberrors.avgce = dsbuf[1];
+            ooberrors.rmserror = dsbuf[2];
+            ooberrors.avgerror = dsbuf[3];
+            ooberrors.avgrelerror = dsbuf[4];
+        }
+
+
+    }
+    public class pca
+    {
+        /*************************************************************************
+        Principal components analysis
+
+        Subroutine  builds  orthogonal  basis  where  first  axis  corresponds  to
+        direction with maximum variance, second axis maximizes variance in subspace
+        orthogonal to first axis and so on.
+
+        It should be noted that, unlike LDA, PCA does not use class labels.
+
+        INPUT PARAMETERS:
+            X           -   dataset, array[0..NPoints-1,0..NVars-1].
+                            matrix contains ONLY INDEPENDENT VARIABLES.
+            NPoints     -   dataset size, NPoints>=0
+            NVars       -   number of independent variables, NVars>=1
+
+        ÂÛÕÎÄÍÛÅ ÏÀÐÀÌÅÒÐÛ:
+            Info        -   return code:
+                            * -4, if SVD subroutine haven't converged
+                            * -1, if wrong parameters has been passed (NPoints<0,
+                                  NVars<1)
+                            *  1, if task is solved
+            S2          -   array[0..NVars-1]. variance values corresponding
+                            to basis vectors.
+            V           -   array[0..NVars-1,0..NVars-1]
+                            matrix, whose columns store basis vectors.
+
+          -- ALGLIB --
+             Copyright 25.08.2008 by Bochkanov Sergey
+        *************************************************************************/
+        public static void pcabuildbasis(double[,] x,
+            int npoints,
+            int nvars,
+            ref int info,
+            ref double[] s2,
+            ref double[,] v)
+        {
+            double[,] a = new double[0,0];
+            double[,] u = new double[0,0];
+            double[,] vt = new double[0,0];
+            double[] m = new double[0];
+            double[] t = new double[0];
+            int i = 0;
+            int j = 0;
+            double mean = 0;
+            double variance = 0;
+            double skewness = 0;
+            double kurtosis = 0;
+            int i_ = 0;
+
+            info = 0;
+            s2 = new double[0];
+            v = new double[0,0];
+
+            
+            //
+            // Check input data
+            //
+            if( npoints<0 || nvars<1 )
+            {
+                info = -1;
+                return;
+            }
+            info = 1;
+            
+            //
+            // Special case: NPoints=0
+            //
+            if( npoints==0 )
+            {
+                s2 = new double[nvars-1+1];
+                v = new double[nvars-1+1, nvars-1+1];
+                for(i=0; i<=nvars-1; i++)
+                {
+                    s2[i] = 0;
+                }
+                for(i=0; i<=nvars-1; i++)
+                {
+                    for(j=0; j<=nvars-1; j++)
+                    {
+                        if( i==j )
+                        {
+                            v[i,j] = 1;
+                        }
+                        else
+                        {
+                            v[i,j] = 0;
+                        }
+                    }
+                }
+                return;
+            }
+            
+            //
+            // Calculate means
+            //
+            m = new double[nvars-1+1];
+            t = new double[npoints-1+1];
+            for(j=0; j<=nvars-1; j++)
+            {
+                for(i_=0; i_<=npoints-1;i_++)
+                {
+                    t[i_] = x[i_,j];
+                }
+                basestat.samplemoments(t, npoints, ref mean, ref variance, ref skewness, ref kurtosis);
+                m[j] = mean;
+            }
+            
+            //
+            // Center, apply SVD, prepare output
+            //
+            a = new double[Math.Max(npoints, nvars)-1+1, nvars-1+1];
+            for(i=0; i<=npoints-1; i++)
+            {
+                for(i_=0; i_<=nvars-1;i_++)
+                {
+                    a[i,i_] = x[i,i_];
+                }
+                for(i_=0; i_<=nvars-1;i_++)
+                {
+                    a[i,i_] = a[i,i_] - m[i_];
+                }
+            }
+            for(i=npoints; i<=nvars-1; i++)
+            {
+                for(j=0; j<=nvars-1; j++)
+                {
+                    a[i,j] = 0;
+                }
+            }
+            if( !svd.rmatrixsvd(a, Math.Max(npoints, nvars), nvars, 0, 1, 2, ref s2, ref u, ref vt) )
+            {
+                info = -4;
+                return;
+            }
+            if( npoints!=1 )
+            {
+                for(i=0; i<=nvars-1; i++)
+                {
+                    s2[i] = math.sqr(s2[i])/(npoints-1);
+                }
+            }
+            v = new double[nvars-1+1, nvars-1+1];
+            blas.copyandtranspose(vt, 0, nvars-1, 0, nvars-1, ref v, 0, nvars-1, 0, nvars-1);
+        }
+
+
+    }
+}
+
Index: /trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.5.0/ALGLIB-3.5.0/diffequations.cs
===================================================================
--- /trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.5.0/ALGLIB-3.5.0/diffequations.cs	(revision 7673)
+++ /trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.5.0/ALGLIB-3.5.0/diffequations.cs	(revision 7673)
@@ -0,0 +1,946 @@
+/*************************************************************************
+Copyright (c) Sergey Bochkanov (ALGLIB project).
+
+>>> SOURCE LICENSE >>>
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation (www.fsf.org); either version 2 of the
+License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+A copy of the GNU General Public License is available at
+http://www.fsf.org/licensing/licenses
+>>> END OF LICENSE >>>
+*************************************************************************/
+#pragma warning disable 162
+#pragma warning disable 219
+using System;
+
+public partial class alglib
+{
+
+
+    /*************************************************************************
+
+    *************************************************************************/
+    public class odesolverstate
+    {
+        //
+        // Public declarations
+        //
+        public bool needdy { get { return _innerobj.needdy; } set { _innerobj.needdy = value; } }
+        public double[] y { get { return _innerobj.y; } }
+        public double[] dy { get { return _innerobj.dy; } }
+        public double x { get { return _innerobj.x; } set { _innerobj.x = value; } }
+
+        public odesolverstate()
+        {
+            _innerobj = new odesolver.odesolverstate();
+        }
+
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private odesolver.odesolverstate _innerobj;
+        public odesolver.odesolverstate innerobj { get { return _innerobj; } }
+        public odesolverstate(odesolver.odesolverstate obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+
+    /*************************************************************************
+
+    *************************************************************************/
+    public class odesolverreport
+    {
+        //
+        // Public declarations
+        //
+        public int nfev { get { return _innerobj.nfev; } set { _innerobj.nfev = value; } }
+        public int terminationtype { get { return _innerobj.terminationtype; } set { _innerobj.terminationtype = value; } }
+
+        public odesolverreport()
+        {
+            _innerobj = new odesolver.odesolverreport();
+        }
+
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private odesolver.odesolverreport _innerobj;
+        public odesolver.odesolverreport innerobj { get { return _innerobj; } }
+        public odesolverreport(odesolver.odesolverreport obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+    /*************************************************************************
+    Cash-Karp adaptive ODE solver.
+
+    This subroutine solves ODE  Y'=f(Y,x)  with  initial  conditions  Y(xs)=Ys
+    (here Y may be single variable or vector of N variables).
+
+    INPUT PARAMETERS:
+        Y       -   initial conditions, array[0..N-1].
+                    contains values of Y[] at X[0]
+        N       -   system size
+        X       -   points at which Y should be tabulated, array[0..M-1]
+                    integrations starts at X[0], ends at X[M-1],  intermediate
+                    values at X[i] are returned too.
+                    SHOULD BE ORDERED BY ASCENDING OR BY DESCENDING!!!!
+        M       -   number of intermediate points + first point + last point:
+                    * M>2 means that you need both Y(X[M-1]) and M-2 values at
+                      intermediate points
+                    * M=2 means that you want just to integrate from  X[0]  to
+                      X[1] and don't interested in intermediate values.
+                    * M=1 means that you don't want to integrate :)
+                      it is degenerate case, but it will be handled correctly.
+                    * M<1 means error
+        Eps     -   tolerance (absolute/relative error on each  step  will  be
+                    less than Eps). When passing:
+                    * Eps>0, it means desired ABSOLUTE error
+                    * Eps<0, it means desired RELATIVE error.  Relative errors
+                      are calculated with respect to maximum values of  Y seen
+                      so far. Be careful to use this criterion  when  starting
+                      from Y[] that are close to zero.
+        H       -   initial  step  lenth,  it  will  be adjusted automatically
+                    after the first  step.  If  H=0,  step  will  be  selected
+                    automatically  (usualy  it  will  be  equal  to  0.001  of
+                    min(x[i]-x[j])).
+
+    OUTPUT PARAMETERS
+        State   -   structure which stores algorithm state between  subsequent
+                    calls of OdeSolverIteration. Used for reverse communication.
+                    This structure should be passed  to the OdeSolverIteration
+                    subroutine.
+
+    SEE ALSO
+        AutoGKSmoothW, AutoGKSingular, AutoGKIteration, AutoGKResults.
+
+
+      -- ALGLIB --
+         Copyright 01.09.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void odesolverrkck(double[] y, int n, double[] x, int m, double eps, double h, out odesolverstate state)
+    {
+        state = new odesolverstate();
+        odesolver.odesolverrkck(y, n, x, m, eps, h, state.innerobj);
+        return;
+    }
+    public static void odesolverrkck(double[] y, double[] x, double eps, double h, out odesolverstate state)
+    {
+        int n;
+        int m;
+
+        state = new odesolverstate();
+        n = ap.len(y);
+        m = ap.len(x);
+        odesolver.odesolverrkck(y, n, x, m, eps, h, state.innerobj);
+
+        return;
+    }
+
+    /*************************************************************************
+    This function provides reverse communication interface
+    Reverse communication interface is not documented or recommended to use.
+    See below for functions which provide better documented API
+    *************************************************************************/
+    public static bool odesolveriteration(odesolverstate state)
+    {
+
+        bool result = odesolver.odesolveriteration(state.innerobj);
+        return result;
+    }
+    /*************************************************************************
+    This function is used to launcn iterations of ODE solver
+
+    It accepts following parameters:
+        diff    -   callback which calculates dy/dx for given y and x
+        obj     -   optional object which is passed to diff; can be NULL
+
+
+      -- ALGLIB --
+         Copyright 01.09.2009 by Bochkanov Sergey
+
+    *************************************************************************/
+    public static void odesolversolve(odesolverstate state, ndimensional_ode_rp diff, object obj)
+    {
+        if( diff==null )
+            throw new alglibexception("ALGLIB: error in 'odesolversolve()' (diff is null)");
+        while( alglib.odesolveriteration(state) )
+        {
+            if( state.needdy )
+            {
+                diff(state.innerobj.y, state.innerobj.x, state.innerobj.dy, obj);
+                continue;
+            }
+            throw new alglibexception("ALGLIB: unexpected error in 'odesolversolve'");
+        }
+    }
+
+
+
+    /*************************************************************************
+    ODE solver results
+
+    Called after OdeSolverIteration returned False.
+
+    INPUT PARAMETERS:
+        State   -   algorithm state (used by OdeSolverIteration).
+
+    OUTPUT PARAMETERS:
+        M       -   number of tabulated values, M>=1
+        XTbl    -   array[0..M-1], values of X
+        YTbl    -   array[0..M-1,0..N-1], values of Y in X[i]
+        Rep     -   solver report:
+                    * Rep.TerminationType completetion code:
+                        * -2    X is not ordered  by  ascending/descending  or
+                                there are non-distinct X[],  i.e.  X[i]=X[i+1]
+                        * -1    incorrect parameters were specified
+                        *  1    task has been solved
+                    * Rep.NFEV contains number of function calculations
+
+      -- ALGLIB --
+         Copyright 01.09.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void odesolverresults(odesolverstate state, out int m, out double[] xtbl, out double[,] ytbl, out odesolverreport rep)
+    {
+        m = 0;
+        xtbl = new double[0];
+        ytbl = new double[0,0];
+        rep = new odesolverreport();
+        odesolver.odesolverresults(state.innerobj, ref m, ref xtbl, ref ytbl, rep.innerobj);
+        return;
+    }
+
+}
+public partial class alglib
+{
+    public class odesolver
+    {
+        public class odesolverstate
+        {
+            public int n;
+            public int m;
+            public double xscale;
+            public double h;
+            public double eps;
+            public bool fraceps;
+            public double[] yc;
+            public double[] escale;
+            public double[] xg;
+            public int solvertype;
+            public bool needdy;
+            public double x;
+            public double[] y;
+            public double[] dy;
+            public double[,] ytbl;
+            public int repterminationtype;
+            public int repnfev;
+            public double[] yn;
+            public double[] yns;
+            public double[] rka;
+            public double[] rkc;
+            public double[] rkcs;
+            public double[,] rkb;
+            public double[,] rkk;
+            public rcommstate rstate;
+            public odesolverstate()
+            {
+                yc = new double[0];
+                escale = new double[0];
+                xg = new double[0];
+                y = new double[0];
+                dy = new double[0];
+                ytbl = new double[0,0];
+                yn = new double[0];
+                yns = new double[0];
+                rka = new double[0];
+                rkc = new double[0];
+                rkcs = new double[0];
+                rkb = new double[0,0];
+                rkk = new double[0,0];
+                rstate = new rcommstate();
+            }
+        };
+
+
+        public class odesolverreport
+        {
+            public int nfev;
+            public int terminationtype;
+        };
+
+
+
+
+        public const double odesolvermaxgrow = 3.0;
+        public const double odesolvermaxshrink = 10.0;
+
+
+        /*************************************************************************
+        Cash-Karp adaptive ODE solver.
+
+        This subroutine solves ODE  Y'=f(Y,x)  with  initial  conditions  Y(xs)=Ys
+        (here Y may be single variable or vector of N variables).
+
+        INPUT PARAMETERS:
+            Y       -   initial conditions, array[0..N-1].
+                        contains values of Y[] at X[0]
+            N       -   system size
+            X       -   points at which Y should be tabulated, array[0..M-1]
+                        integrations starts at X[0], ends at X[M-1],  intermediate
+                        values at X[i] are returned too.
+                        SHOULD BE ORDERED BY ASCENDING OR BY DESCENDING!!!!
+            M       -   number of intermediate points + first point + last point:
+                        * M>2 means that you need both Y(X[M-1]) and M-2 values at
+                          intermediate points
+                        * M=2 means that you want just to integrate from  X[0]  to
+                          X[1] and don't interested in intermediate values.
+                        * M=1 means that you don't want to integrate :)
+                          it is degenerate case, but it will be handled correctly.
+                        * M<1 means error
+            Eps     -   tolerance (absolute/relative error on each  step  will  be
+                        less than Eps). When passing:
+                        * Eps>0, it means desired ABSOLUTE error
+                        * Eps<0, it means desired RELATIVE error.  Relative errors
+                          are calculated with respect to maximum values of  Y seen
+                          so far. Be careful to use this criterion  when  starting
+                          from Y[] that are close to zero.
+            H       -   initial  step  lenth,  it  will  be adjusted automatically
+                        after the first  step.  If  H=0,  step  will  be  selected
+                        automatically  (usualy  it  will  be  equal  to  0.001  of
+                        min(x[i]-x[j])).
+
+        OUTPUT PARAMETERS
+            State   -   structure which stores algorithm state between  subsequent
+                        calls of OdeSolverIteration. Used for reverse communication.
+                        This structure should be passed  to the OdeSolverIteration
+                        subroutine.
+
+        SEE ALSO
+            AutoGKSmoothW, AutoGKSingular, AutoGKIteration, AutoGKResults.
+
+
+          -- ALGLIB --
+             Copyright 01.09.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void odesolverrkck(double[] y,
+            int n,
+            double[] x,
+            int m,
+            double eps,
+            double h,
+            odesolverstate state)
+        {
+            alglib.ap.assert(n>=1, "ODESolverRKCK: N<1!");
+            alglib.ap.assert(m>=1, "ODESolverRKCK: M<1!");
+            alglib.ap.assert(alglib.ap.len(y)>=n, "ODESolverRKCK: Length(Y)<N!");
+            alglib.ap.assert(alglib.ap.len(x)>=m, "ODESolverRKCK: Length(X)<M!");
+            alglib.ap.assert(apserv.isfinitevector(y, n), "ODESolverRKCK: Y contains infinite or NaN values!");
+            alglib.ap.assert(apserv.isfinitevector(x, m), "ODESolverRKCK: Y contains infinite or NaN values!");
+            alglib.ap.assert(math.isfinite(eps), "ODESolverRKCK: Eps is not finite!");
+            alglib.ap.assert((double)(eps)!=(double)(0), "ODESolverRKCK: Eps is zero!");
+            alglib.ap.assert(math.isfinite(h), "ODESolverRKCK: H is not finite!");
+            odesolverinit(0, y, n, x, m, eps, h, state);
+        }
+
+
+        /*************************************************************************
+
+          -- ALGLIB --
+             Copyright 01.09.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static bool odesolveriteration(odesolverstate state)
+        {
+            bool result = new bool();
+            int n = 0;
+            int m = 0;
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            double xc = 0;
+            double v = 0;
+            double h = 0;
+            double h2 = 0;
+            bool gridpoint = new bool();
+            double err = 0;
+            double maxgrowpow = 0;
+            int klimit = 0;
+            int i_ = 0;
+
+            
+            //
+            // Reverse communication preparations
+            // I know it looks ugly, but it works the same way
+            // anywhere from C++ to Python.
+            //
+            // This code initializes locals by:
+            // * random values determined during code
+            //   generation - on first subroutine call
+            // * values from previous call - on subsequent calls
+            //
+            if( state.rstate.stage>=0 )
+            {
+                n = state.rstate.ia[0];
+                m = state.rstate.ia[1];
+                i = state.rstate.ia[2];
+                j = state.rstate.ia[3];
+                k = state.rstate.ia[4];
+                klimit = state.rstate.ia[5];
+                gridpoint = state.rstate.ba[0];
+                xc = state.rstate.ra[0];
+                v = state.rstate.ra[1];
+                h = state.rstate.ra[2];
+                h2 = state.rstate.ra[3];
+                err = state.rstate.ra[4];
+                maxgrowpow = state.rstate.ra[5];
+            }
+            else
+            {
+                n = -983;
+                m = -989;
+                i = -834;
+                j = 900;
+                k = -287;
+                klimit = 364;
+                gridpoint = false;
+                xc = -338;
+                v = -686;
+                h = 912;
+                h2 = 585;
+                err = 497;
+                maxgrowpow = -271;
+            }
+            if( state.rstate.stage==0 )
+            {
+                goto lbl_0;
+            }
+            
+            //
+            // Routine body
+            //
+            
+            //
+            // prepare
+            //
+            if( state.repterminationtype!=0 )
+            {
+                result = false;
+                return result;
+            }
+            n = state.n;
+            m = state.m;
+            h = state.h;
+            maxgrowpow = Math.Pow(odesolvermaxgrow, 5);
+            state.repnfev = 0;
+            
+            //
+            // some preliminary checks for internal errors
+            // after this we assume that H>0 and M>1
+            //
+            alglib.ap.assert((double)(state.h)>(double)(0), "ODESolver: internal error");
+            alglib.ap.assert(m>1, "ODESolverIteration: internal error");
+            
+            //
+            // choose solver
+            //
+            if( state.solvertype!=0 )
+            {
+                goto lbl_1;
+            }
+            
+            //
+            // Cask-Karp solver
+            // Prepare coefficients table.
+            // Check it for errors
+            //
+            state.rka = new double[6];
+            state.rka[0] = 0;
+            state.rka[1] = (double)1/(double)5;
+            state.rka[2] = (double)3/(double)10;
+            state.rka[3] = (double)3/(double)5;
+            state.rka[4] = 1;
+            state.rka[5] = (double)7/(double)8;
+            state.rkb = new double[6, 5];
+            state.rkb[1,0] = (double)1/(double)5;
+            state.rkb[2,0] = (double)3/(double)40;
+            state.rkb[2,1] = (double)9/(double)40;
+            state.rkb[3,0] = (double)3/(double)10;
+            state.rkb[3,1] = -((double)9/(double)10);
+            state.rkb[3,2] = (double)6/(double)5;
+            state.rkb[4,0] = -((double)11/(double)54);
+            state.rkb[4,1] = (double)5/(double)2;
+            state.rkb[4,2] = -((double)70/(double)27);
+            state.rkb[4,3] = (double)35/(double)27;
+            state.rkb[5,0] = (double)1631/(double)55296;
+            state.rkb[5,1] = (double)175/(double)512;
+            state.rkb[5,2] = (double)575/(double)13824;
+            state.rkb[5,3] = (double)44275/(double)110592;
+            state.rkb[5,4] = (double)253/(double)4096;
+            state.rkc = new double[6];
+            state.rkc[0] = (double)37/(double)378;
+            state.rkc[1] = 0;
+            state.rkc[2] = (double)250/(double)621;
+            state.rkc[3] = (double)125/(double)594;
+            state.rkc[4] = 0;
+            state.rkc[5] = (double)512/(double)1771;
+            state.rkcs = new double[6];
+            state.rkcs[0] = (double)2825/(double)27648;
+            state.rkcs[1] = 0;
+            state.rkcs[2] = (double)18575/(double)48384;
+            state.rkcs[3] = (double)13525/(double)55296;
+            state.rkcs[4] = (double)277/(double)14336;
+            state.rkcs[5] = (double)1/(double)4;
+            state.rkk = new double[6, n];
+            
+            //
+            // Main cycle consists of two iterations:
+            // * outer where we travel from X[i-1] to X[i]
+            // * inner where we travel inside [X[i-1],X[i]]
+            //
+            state.ytbl = new double[m, n];
+            state.escale = new double[n];
+            state.yn = new double[n];
+            state.yns = new double[n];
+            xc = state.xg[0];
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.ytbl[0,i_] = state.yc[i_];
+            }
+            for(j=0; j<=n-1; j++)
+            {
+                state.escale[j] = 0;
+            }
+            i = 1;
+        lbl_3:
+            if( i>m-1 )
+            {
+                goto lbl_5;
+            }
+            
+            //
+            // begin inner iteration
+            //
+        lbl_6:
+            if( false )
+            {
+                goto lbl_7;
+            }
+            
+            //
+            // truncate step if needed (beyond right boundary).
+            // determine should we store X or not
+            //
+            if( (double)(xc+h)>=(double)(state.xg[i]) )
+            {
+                h = state.xg[i]-xc;
+                gridpoint = true;
+            }
+            else
+            {
+                gridpoint = false;
+            }
+            
+            //
+            // Update error scale maximums
+            //
+            // These maximums are initialized by zeros,
+            // then updated every iterations.
+            //
+            for(j=0; j<=n-1; j++)
+            {
+                state.escale[j] = Math.Max(state.escale[j], Math.Abs(state.yc[j]));
+            }
+            
+            //
+            // make one step:
+            // 1. calculate all info needed to do step
+            // 2. update errors scale maximums using values/derivatives
+            //    obtained during (1)
+            //
+            // Take into account that we use scaling of X to reduce task
+            // to the form where x[0] < x[1] < ... < x[n-1]. So X is
+            // replaced by x=xscale*t, and dy/dx=f(y,x) is replaced
+            // by dy/dt=xscale*f(y,xscale*t).
+            //
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.yn[i_] = state.yc[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.yns[i_] = state.yc[i_];
+            }
+            k = 0;
+        lbl_8:
+            if( k>5 )
+            {
+                goto lbl_10;
+            }
+            
+            //
+            // prepare data for the next update of YN/YNS
+            //
+            state.x = state.xscale*(xc+state.rka[k]*h);
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.y[i_] = state.yc[i_];
+            }
+            for(j=0; j<=k-1; j++)
+            {
+                v = state.rkb[k,j];
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    state.y[i_] = state.y[i_] + v*state.rkk[j,i_];
+                }
+            }
+            state.needdy = true;
+            state.rstate.stage = 0;
+            goto lbl_rcomm;
+        lbl_0:
+            state.needdy = false;
+            state.repnfev = state.repnfev+1;
+            v = h*state.xscale;
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.rkk[k,i_] = v*state.dy[i_];
+            }
+            
+            //
+            // update YN/YNS
+            //
+            v = state.rkc[k];
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.yn[i_] = state.yn[i_] + v*state.rkk[k,i_];
+            }
+            v = state.rkcs[k];
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.yns[i_] = state.yns[i_] + v*state.rkk[k,i_];
+            }
+            k = k+1;
+            goto lbl_8;
+        lbl_10:
+            
+            //
+            // estimate error
+            //
+            err = 0;
+            for(j=0; j<=n-1; j++)
+            {
+                if( !state.fraceps )
+                {
+                    
+                    //
+                    // absolute error is estimated
+                    //
+                    err = Math.Max(err, Math.Abs(state.yn[j]-state.yns[j]));
+                }
+                else
+                {
+                    
+                    //
+                    // Relative error is estimated
+                    //
+                    v = state.escale[j];
+                    if( (double)(v)==(double)(0) )
+                    {
+                        v = 1;
+                    }
+                    err = Math.Max(err, Math.Abs(state.yn[j]-state.yns[j])/v);
+                }
+            }
+            
+            //
+            // calculate new step, restart if necessary
+            //
+            if( (double)(maxgrowpow*err)<=(double)(state.eps) )
+            {
+                h2 = odesolvermaxgrow*h;
+            }
+            else
+            {
+                h2 = h*Math.Pow(state.eps/err, 0.2);
+            }
+            if( (double)(h2)<(double)(h/odesolvermaxshrink) )
+            {
+                h2 = h/odesolvermaxshrink;
+            }
+            if( (double)(err)>(double)(state.eps) )
+            {
+                h = h2;
+                goto lbl_6;
+            }
+            
+            //
+            // advance position
+            //
+            xc = xc+h;
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.yc[i_] = state.yn[i_];
+            }
+            
+            //
+            // update H
+            //
+            h = h2;
+            
+            //
+            // break on grid point
+            //
+            if( gridpoint )
+            {
+                goto lbl_7;
+            }
+            goto lbl_6;
+        lbl_7:
+            
+            //
+            // save result
+            //
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.ytbl[i,i_] = state.yc[i_];
+            }
+            i = i+1;
+            goto lbl_3;
+        lbl_5:
+            state.repterminationtype = 1;
+            result = false;
+            return result;
+        lbl_1:
+            result = false;
+            return result;
+            
+            //
+            // Saving state
+            //
+        lbl_rcomm:
+            result = true;
+            state.rstate.ia[0] = n;
+            state.rstate.ia[1] = m;
+            state.rstate.ia[2] = i;
+            state.rstate.ia[3] = j;
+            state.rstate.ia[4] = k;
+            state.rstate.ia[5] = klimit;
+            state.rstate.ba[0] = gridpoint;
+            state.rstate.ra[0] = xc;
+            state.rstate.ra[1] = v;
+            state.rstate.ra[2] = h;
+            state.rstate.ra[3] = h2;
+            state.rstate.ra[4] = err;
+            state.rstate.ra[5] = maxgrowpow;
+            return result;
+        }
+
+
+        /*************************************************************************
+        ODE solver results
+
+        Called after OdeSolverIteration returned False.
+
+        INPUT PARAMETERS:
+            State   -   algorithm state (used by OdeSolverIteration).
+
+        OUTPUT PARAMETERS:
+            M       -   number of tabulated values, M>=1
+            XTbl    -   array[0..M-1], values of X
+            YTbl    -   array[0..M-1,0..N-1], values of Y in X[i]
+            Rep     -   solver report:
+                        * Rep.TerminationType completetion code:
+                            * -2    X is not ordered  by  ascending/descending  or
+                                    there are non-distinct X[],  i.e.  X[i]=X[i+1]
+                            * -1    incorrect parameters were specified
+                            *  1    task has been solved
+                        * Rep.NFEV contains number of function calculations
+
+          -- ALGLIB --
+             Copyright 01.09.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void odesolverresults(odesolverstate state,
+            ref int m,
+            ref double[] xtbl,
+            ref double[,] ytbl,
+            odesolverreport rep)
+        {
+            double v = 0;
+            int i = 0;
+            int i_ = 0;
+
+            m = 0;
+            xtbl = new double[0];
+            ytbl = new double[0,0];
+
+            rep.terminationtype = state.repterminationtype;
+            if( rep.terminationtype>0 )
+            {
+                m = state.m;
+                rep.nfev = state.repnfev;
+                xtbl = new double[state.m];
+                v = state.xscale;
+                for(i_=0; i_<=state.m-1;i_++)
+                {
+                    xtbl[i_] = v*state.xg[i_];
+                }
+                ytbl = new double[state.m, state.n];
+                for(i=0; i<=state.m-1; i++)
+                {
+                    for(i_=0; i_<=state.n-1;i_++)
+                    {
+                        ytbl[i,i_] = state.ytbl[i,i_];
+                    }
+                }
+            }
+            else
+            {
+                rep.nfev = 0;
+            }
+        }
+
+
+        /*************************************************************************
+        Internal initialization subroutine
+        *************************************************************************/
+        private static void odesolverinit(int solvertype,
+            double[] y,
+            int n,
+            double[] x,
+            int m,
+            double eps,
+            double h,
+            odesolverstate state)
+        {
+            int i = 0;
+            double v = 0;
+            int i_ = 0;
+
+            
+            //
+            // Prepare RComm
+            //
+            state.rstate.ia = new int[5+1];
+            state.rstate.ba = new bool[0+1];
+            state.rstate.ra = new double[5+1];
+            state.rstate.stage = -1;
+            state.needdy = false;
+            
+            //
+            // check parameters.
+            //
+            if( (n<=0 || m<1) || (double)(eps)==(double)(0) )
+            {
+                state.repterminationtype = -1;
+                return;
+            }
+            if( (double)(h)<(double)(0) )
+            {
+                h = -h;
+            }
+            
+            //
+            // quick exit if necessary.
+            // after this block we assume that M>1
+            //
+            if( m==1 )
+            {
+                state.repnfev = 0;
+                state.repterminationtype = 1;
+                state.ytbl = new double[1, n];
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    state.ytbl[0,i_] = y[i_];
+                }
+                state.xg = new double[m];
+                for(i_=0; i_<=m-1;i_++)
+                {
+                    state.xg[i_] = x[i_];
+                }
+                return;
+            }
+            
+            //
+            // check again: correct order of X[]
+            //
+            if( (double)(x[1])==(double)(x[0]) )
+            {
+                state.repterminationtype = -2;
+                return;
+            }
+            for(i=1; i<=m-1; i++)
+            {
+                if( ((double)(x[1])>(double)(x[0]) && (double)(x[i])<=(double)(x[i-1])) || ((double)(x[1])<(double)(x[0]) && (double)(x[i])>=(double)(x[i-1])) )
+                {
+                    state.repterminationtype = -2;
+                    return;
+                }
+            }
+            
+            //
+            // auto-select H if necessary
+            //
+            if( (double)(h)==(double)(0) )
+            {
+                v = Math.Abs(x[1]-x[0]);
+                for(i=2; i<=m-1; i++)
+                {
+                    v = Math.Min(v, Math.Abs(x[i]-x[i-1]));
+                }
+                h = 0.001*v;
+            }
+            
+            //
+            // store parameters
+            //
+            state.n = n;
+            state.m = m;
+            state.h = h;
+            state.eps = Math.Abs(eps);
+            state.fraceps = (double)(eps)<(double)(0);
+            state.xg = new double[m];
+            for(i_=0; i_<=m-1;i_++)
+            {
+                state.xg[i_] = x[i_];
+            }
+            if( (double)(x[1])>(double)(x[0]) )
+            {
+                state.xscale = 1;
+            }
+            else
+            {
+                state.xscale = -1;
+                for(i_=0; i_<=m-1;i_++)
+                {
+                    state.xg[i_] = -1*state.xg[i_];
+                }
+            }
+            state.yc = new double[n];
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.yc[i_] = y[i_];
+            }
+            state.solvertype = solvertype;
+            state.repterminationtype = 0;
+            
+            //
+            // Allocate arrays
+            //
+            state.y = new double[n];
+            state.dy = new double[n];
+        }
+
+
+    }
+}
+
Index: /trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.5.0/ALGLIB-3.5.0/fasttransforms.cs
===================================================================
--- /trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.5.0/ALGLIB-3.5.0/fasttransforms.cs	(revision 7673)
+++ /trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.5.0/ALGLIB-3.5.0/fasttransforms.cs	(revision 7673)
@@ -0,0 +1,3289 @@
+/*************************************************************************
+Copyright (c) Sergey Bochkanov (ALGLIB project).
+
+>>> SOURCE LICENSE >>>
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation (www.fsf.org); either version 2 of the
+License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+A copy of the GNU General Public License is available at
+http://www.fsf.org/licensing/licenses
+>>> END OF LICENSE >>>
+*************************************************************************/
+#pragma warning disable 162
+#pragma warning disable 219
+using System;
+
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    1-dimensional complex FFT.
+
+    Array size N may be arbitrary number (composite or prime).  Composite  N's
+    are handled with cache-oblivious variation of  a  Cooley-Tukey  algorithm.
+    Small prime-factors are transformed using hard coded  codelets (similar to
+    FFTW codelets, but without low-level  optimization),  large  prime-factors
+    are handled with Bluestein's algorithm.
+
+    Fastests transforms are for smooth N's (prime factors are 2, 3,  5  only),
+    most fast for powers of 2. When N have prime factors  larger  than  these,
+    but orders of magnitude smaller than N, computations will be about 4 times
+    slower than for nearby highly composite N's. When N itself is prime, speed
+    will be 6 times lower.
+
+    Algorithm has O(N*logN) complexity for any N (composite or prime).
+
+    INPUT PARAMETERS
+        A   -   array[0..N-1] - complex function to be transformed
+        N   -   problem size
+
+    OUTPUT PARAMETERS
+        A   -   DFT of a input array, array[0..N-1]
+                A_out[j] = SUM(A_in[k]*exp(-2*pi*sqrt(-1)*j*k/N), k = 0..N-1)
+
+
+      -- ALGLIB --
+         Copyright 29.05.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void fftc1d(ref complex[] a, int n)
+    {
+
+        fft.fftc1d(ref a, n);
+        return;
+    }
+    public static void fftc1d(ref complex[] a)
+    {
+        int n;
+
+
+        n = ap.len(a);
+        fft.fftc1d(ref a, n);
+
+        return;
+    }
+
+    /*************************************************************************
+    1-dimensional complex inverse FFT.
+
+    Array size N may be arbitrary number (composite or prime).  Algorithm  has
+    O(N*logN) complexity for any N (composite or prime).
+
+    See FFTC1D() description for more information about algorithm performance.
+
+    INPUT PARAMETERS
+        A   -   array[0..N-1] - complex array to be transformed
+        N   -   problem size
+
+    OUTPUT PARAMETERS
+        A   -   inverse DFT of a input array, array[0..N-1]
+                A_out[j] = SUM(A_in[k]/N*exp(+2*pi*sqrt(-1)*j*k/N), k = 0..N-1)
+
+
+      -- ALGLIB --
+         Copyright 29.05.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void fftc1dinv(ref complex[] a, int n)
+    {
+
+        fft.fftc1dinv(ref a, n);
+        return;
+    }
+    public static void fftc1dinv(ref complex[] a)
+    {
+        int n;
+
+
+        n = ap.len(a);
+        fft.fftc1dinv(ref a, n);
+
+        return;
+    }
+
+    /*************************************************************************
+    1-dimensional real FFT.
+
+    Algorithm has O(N*logN) complexity for any N (composite or prime).
+
+    INPUT PARAMETERS
+        A   -   array[0..N-1] - real function to be transformed
+        N   -   problem size
+
+    OUTPUT PARAMETERS
+        F   -   DFT of a input array, array[0..N-1]
+                F[j] = SUM(A[k]*exp(-2*pi*sqrt(-1)*j*k/N), k = 0..N-1)
+
+    NOTE:
+        F[] satisfies symmetry property F[k] = conj(F[N-k]),  so just one half
+    of  array  is  usually needed. But for convinience subroutine returns full
+    complex array (with frequencies above N/2), so its result may be  used  by
+    other FFT-related subroutines.
+
+
+      -- ALGLIB --
+         Copyright 01.06.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void fftr1d(double[] a, int n, out complex[] f)
+    {
+        f = new complex[0];
+        fft.fftr1d(a, n, ref f);
+        return;
+    }
+    public static void fftr1d(double[] a, out complex[] f)
+    {
+        int n;
+
+        f = new complex[0];
+        n = ap.len(a);
+        fft.fftr1d(a, n, ref f);
+
+        return;
+    }
+
+    /*************************************************************************
+    1-dimensional real inverse FFT.
+
+    Algorithm has O(N*logN) complexity for any N (composite or prime).
+
+    INPUT PARAMETERS
+        F   -   array[0..floor(N/2)] - frequencies from forward real FFT
+        N   -   problem size
+
+    OUTPUT PARAMETERS
+        A   -   inverse DFT of a input array, array[0..N-1]
+
+    NOTE:
+        F[] should satisfy symmetry property F[k] = conj(F[N-k]), so just  one
+    half of frequencies array is needed - elements from 0 to floor(N/2).  F[0]
+    is ALWAYS real. If N is even F[floor(N/2)] is real too. If N is odd,  then
+    F[floor(N/2)] has no special properties.
+
+    Relying on properties noted above, FFTR1DInv subroutine uses only elements
+    from 0th to floor(N/2)-th. It ignores imaginary part of F[0],  and in case
+    N is even it ignores imaginary part of F[floor(N/2)] too.
+
+    When you call this function using full arguments list - "FFTR1DInv(F,N,A)"
+    - you can pass either either frequencies array with N elements or  reduced
+    array with roughly N/2 elements - subroutine will  successfully  transform
+    both.
+
+    If you call this function using reduced arguments list -  "FFTR1DInv(F,A)"
+    - you must pass FULL array with N elements (although higher  N/2 are still
+    not used) because array size is used to automatically determine FFT length
+
+
+      -- ALGLIB --
+         Copyright 01.06.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void fftr1dinv(complex[] f, int n, out double[] a)
+    {
+        a = new double[0];
+        fft.fftr1dinv(f, n, ref a);
+        return;
+    }
+    public static void fftr1dinv(complex[] f, out double[] a)
+    {
+        int n;
+
+        a = new double[0];
+        n = ap.len(f);
+        fft.fftr1dinv(f, n, ref a);
+
+        return;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    1-dimensional complex convolution.
+
+    For given A/B returns conv(A,B) (non-circular). Subroutine can automatically
+    choose between three implementations: straightforward O(M*N)  formula  for
+    very small N (or M), overlap-add algorithm for  cases  where  max(M,N)  is
+    significantly larger than min(M,N), but O(M*N) algorithm is too slow,  and
+    general FFT-based formula for cases where two previois algorithms are  too
+    slow.
+
+    Algorithm has max(M,N)*log(max(M,N)) complexity for any M/N.
+
+    INPUT PARAMETERS
+        A   -   array[0..M-1] - complex function to be transformed
+        M   -   problem size
+        B   -   array[0..N-1] - complex function to be transformed
+        N   -   problem size
+
+    OUTPUT PARAMETERS
+        R   -   convolution: A*B. array[0..N+M-2].
+
+    NOTE:
+        It is assumed that A is zero at T<0, B is zero too.  If  one  or  both
+    functions have non-zero values at negative T's, you  can  still  use  this
+    subroutine - just shift its result correspondingly.
+
+      -- ALGLIB --
+         Copyright 21.07.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void convc1d(complex[] a, int m, complex[] b, int n, out complex[] r)
+    {
+        r = new complex[0];
+        conv.convc1d(a, m, b, n, ref r);
+        return;
+    }
+
+    /*************************************************************************
+    1-dimensional complex non-circular deconvolution (inverse of ConvC1D()).
+
+    Algorithm has M*log(M)) complexity for any M (composite or prime).
+
+    INPUT PARAMETERS
+        A   -   array[0..M-1] - convolved signal, A = conv(R, B)
+        M   -   convolved signal length
+        B   -   array[0..N-1] - response
+        N   -   response length, N<=M
+
+    OUTPUT PARAMETERS
+        R   -   deconvolved signal. array[0..M-N].
+
+    NOTE:
+        deconvolution is unstable process and may result in division  by  zero
+    (if your response function is degenerate, i.e. has zero Fourier coefficient).
+
+    NOTE:
+        It is assumed that A is zero at T<0, B is zero too.  If  one  or  both
+    functions have non-zero values at negative T's, you  can  still  use  this
+    subroutine - just shift its result correspondingly.
+
+      -- ALGLIB --
+         Copyright 21.07.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void convc1dinv(complex[] a, int m, complex[] b, int n, out complex[] r)
+    {
+        r = new complex[0];
+        conv.convc1dinv(a, m, b, n, ref r);
+        return;
+    }
+
+    /*************************************************************************
+    1-dimensional circular complex convolution.
+
+    For given S/R returns conv(S,R) (circular). Algorithm has linearithmic
+    complexity for any M/N.
+
+    IMPORTANT:  normal convolution is commutative,  i.e.   it  is symmetric  -
+    conv(A,B)=conv(B,A).  Cyclic convolution IS NOT.  One function - S - is  a
+    signal,  periodic function, and another - R - is a response,  non-periodic
+    function with limited length.
+
+    INPUT PARAMETERS
+        S   -   array[0..M-1] - complex periodic signal
+        M   -   problem size
+        B   -   array[0..N-1] - complex non-periodic response
+        N   -   problem size
+
+    OUTPUT PARAMETERS
+        R   -   convolution: A*B. array[0..M-1].
+
+    NOTE:
+        It is assumed that B is zero at T<0. If  it  has  non-zero  values  at
+    negative T's, you can still use this subroutine - just  shift  its  result
+    correspondingly.
+
+      -- ALGLIB --
+         Copyright 21.07.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void convc1dcircular(complex[] s, int m, complex[] r, int n, out complex[] c)
+    {
+        c = new complex[0];
+        conv.convc1dcircular(s, m, r, n, ref c);
+        return;
+    }
+
+    /*************************************************************************
+    1-dimensional circular complex deconvolution (inverse of ConvC1DCircular()).
+
+    Algorithm has M*log(M)) complexity for any M (composite or prime).
+
+    INPUT PARAMETERS
+        A   -   array[0..M-1] - convolved periodic signal, A = conv(R, B)
+        M   -   convolved signal length
+        B   -   array[0..N-1] - non-periodic response
+        N   -   response length
+
+    OUTPUT PARAMETERS
+        R   -   deconvolved signal. array[0..M-1].
+
+    NOTE:
+        deconvolution is unstable process and may result in division  by  zero
+    (if your response function is degenerate, i.e. has zero Fourier coefficient).
+
+    NOTE:
+        It is assumed that B is zero at T<0. If  it  has  non-zero  values  at
+    negative T's, you can still use this subroutine - just  shift  its  result
+    correspondingly.
+
+      -- ALGLIB --
+         Copyright 21.07.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void convc1dcircularinv(complex[] a, int m, complex[] b, int n, out complex[] r)
+    {
+        r = new complex[0];
+        conv.convc1dcircularinv(a, m, b, n, ref r);
+        return;
+    }
+
+    /*************************************************************************
+    1-dimensional real convolution.
+
+    Analogous to ConvC1D(), see ConvC1D() comments for more details.
+
+    INPUT PARAMETERS
+        A   -   array[0..M-1] - real function to be transformed
+        M   -   problem size
+        B   -   array[0..N-1] - real function to be transformed
+        N   -   problem size
+
+    OUTPUT PARAMETERS
+        R   -   convolution: A*B. array[0..N+M-2].
+
+    NOTE:
+        It is assumed that A is zero at T<0, B is zero too.  If  one  or  both
+    functions have non-zero values at negative T's, you  can  still  use  this
+    subroutine - just shift its result correspondingly.
+
+      -- ALGLIB --
+         Copyright 21.07.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void convr1d(double[] a, int m, double[] b, int n, out double[] r)
+    {
+        r = new double[0];
+        conv.convr1d(a, m, b, n, ref r);
+        return;
+    }
+
+    /*************************************************************************
+    1-dimensional real deconvolution (inverse of ConvC1D()).
+
+    Algorithm has M*log(M)) complexity for any M (composite or prime).
+
+    INPUT PARAMETERS
+        A   -   array[0..M-1] - convolved signal, A = conv(R, B)
+        M   -   convolved signal length
+        B   -   array[0..N-1] - response
+        N   -   response length, N<=M
+
+    OUTPUT PARAMETERS
+        R   -   deconvolved signal. array[0..M-N].
+
+    NOTE:
+        deconvolution is unstable process and may result in division  by  zero
+    (if your response function is degenerate, i.e. has zero Fourier coefficient).
+
+    NOTE:
+        It is assumed that A is zero at T<0, B is zero too.  If  one  or  both
+    functions have non-zero values at negative T's, you  can  still  use  this
+    subroutine - just shift its result correspondingly.
+
+      -- ALGLIB --
+         Copyright 21.07.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void convr1dinv(double[] a, int m, double[] b, int n, out double[] r)
+    {
+        r = new double[0];
+        conv.convr1dinv(a, m, b, n, ref r);
+        return;
+    }
+
+    /*************************************************************************
+    1-dimensional circular real convolution.
+
+    Analogous to ConvC1DCircular(), see ConvC1DCircular() comments for more details.
+
+    INPUT PARAMETERS
+        S   -   array[0..M-1] - real signal
+        M   -   problem size
+        B   -   array[0..N-1] - real response
+        N   -   problem size
+
+    OUTPUT PARAMETERS
+        R   -   convolution: A*B. array[0..M-1].
+
+    NOTE:
+        It is assumed that B is zero at T<0. If  it  has  non-zero  values  at
+    negative T's, you can still use this subroutine - just  shift  its  result
+    correspondingly.
+
+      -- ALGLIB --
+         Copyright 21.07.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void convr1dcircular(double[] s, int m, double[] r, int n, out double[] c)
+    {
+        c = new double[0];
+        conv.convr1dcircular(s, m, r, n, ref c);
+        return;
+    }
+
+    /*************************************************************************
+    1-dimensional complex deconvolution (inverse of ConvC1D()).
+
+    Algorithm has M*log(M)) complexity for any M (composite or prime).
+
+    INPUT PARAMETERS
+        A   -   array[0..M-1] - convolved signal, A = conv(R, B)
+        M   -   convolved signal length
+        B   -   array[0..N-1] - response
+        N   -   response length
+
+    OUTPUT PARAMETERS
+        R   -   deconvolved signal. array[0..M-N].
+
+    NOTE:
+        deconvolution is unstable process and may result in division  by  zero
+    (if your response function is degenerate, i.e. has zero Fourier coefficient).
+
+    NOTE:
+        It is assumed that B is zero at T<0. If  it  has  non-zero  values  at
+    negative T's, you can still use this subroutine - just  shift  its  result
+    correspondingly.
+
+      -- ALGLIB --
+         Copyright 21.07.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void convr1dcircularinv(double[] a, int m, double[] b, int n, out double[] r)
+    {
+        r = new double[0];
+        conv.convr1dcircularinv(a, m, b, n, ref r);
+        return;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    1-dimensional complex cross-correlation.
+
+    For given Pattern/Signal returns corr(Pattern,Signal) (non-circular).
+
+    Correlation is calculated using reduction to  convolution.  Algorithm with
+    max(N,N)*log(max(N,N)) complexity is used (see  ConvC1D()  for  more  info
+    about performance).
+
+    IMPORTANT:
+        for  historical reasons subroutine accepts its parameters in  reversed
+        order: CorrC1D(Signal, Pattern) = Pattern x Signal (using  traditional
+        definition of cross-correlation, denoting cross-correlation as "x").
+
+    INPUT PARAMETERS
+        Signal  -   array[0..N-1] - complex function to be transformed,
+                    signal containing pattern
+        N       -   problem size
+        Pattern -   array[0..M-1] - complex function to be transformed,
+                    pattern to search withing signal
+        M       -   problem size
+
+    OUTPUT PARAMETERS
+        R       -   cross-correlation, array[0..N+M-2]:
+                    * positive lags are stored in R[0..N-1],
+                      R[i] = sum(conj(pattern[j])*signal[i+j]
+                    * negative lags are stored in R[N..N+M-2],
+                      R[N+M-1-i] = sum(conj(pattern[j])*signal[-i+j]
+
+    NOTE:
+        It is assumed that pattern domain is [0..M-1].  If Pattern is non-zero
+    on [-K..M-1],  you can still use this subroutine, just shift result by K.
+
+      -- ALGLIB --
+         Copyright 21.07.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void corrc1d(complex[] signal, int n, complex[] pattern, int m, out complex[] r)
+    {
+        r = new complex[0];
+        corr.corrc1d(signal, n, pattern, m, ref r);
+        return;
+    }
+
+    /*************************************************************************
+    1-dimensional circular complex cross-correlation.
+
+    For given Pattern/Signal returns corr(Pattern,Signal) (circular).
+    Algorithm has linearithmic complexity for any M/N.
+
+    IMPORTANT:
+        for  historical reasons subroutine accepts its parameters in  reversed
+        order:   CorrC1DCircular(Signal, Pattern) = Pattern x Signal    (using
+        traditional definition of cross-correlation, denoting cross-correlation
+        as "x").
+
+    INPUT PARAMETERS
+        Signal  -   array[0..N-1] - complex function to be transformed,
+                    periodic signal containing pattern
+        N       -   problem size
+        Pattern -   array[0..M-1] - complex function to be transformed,
+                    non-periodic pattern to search withing signal
+        M       -   problem size
+
+    OUTPUT PARAMETERS
+        R   -   convolution: A*B. array[0..M-1].
+
+
+      -- ALGLIB --
+         Copyright 21.07.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void corrc1dcircular(complex[] signal, int m, complex[] pattern, int n, out complex[] c)
+    {
+        c = new complex[0];
+        corr.corrc1dcircular(signal, m, pattern, n, ref c);
+        return;
+    }
+
+    /*************************************************************************
+    1-dimensional real cross-correlation.
+
+    For given Pattern/Signal returns corr(Pattern,Signal) (non-circular).
+
+    Correlation is calculated using reduction to  convolution.  Algorithm with
+    max(N,N)*log(max(N,N)) complexity is used (see  ConvC1D()  for  more  info
+    about performance).
+
+    IMPORTANT:
+        for  historical reasons subroutine accepts its parameters in  reversed
+        order: CorrR1D(Signal, Pattern) = Pattern x Signal (using  traditional
+        definition of cross-correlation, denoting cross-correlation as "x").
+
+    INPUT PARAMETERS
+        Signal  -   array[0..N-1] - real function to be transformed,
+                    signal containing pattern
+        N       -   problem size
+        Pattern -   array[0..M-1] - real function to be transformed,
+                    pattern to search withing signal
+        M       -   problem size
+
+    OUTPUT PARAMETERS
+        R       -   cross-correlation, array[0..N+M-2]:
+                    * positive lags are stored in R[0..N-1],
+                      R[i] = sum(pattern[j]*signal[i+j]
+                    * negative lags are stored in R[N..N+M-2],
+                      R[N+M-1-i] = sum(pattern[j]*signal[-i+j]
+
+    NOTE:
+        It is assumed that pattern domain is [0..M-1].  If Pattern is non-zero
+    on [-K..M-1],  you can still use this subroutine, just shift result by K.
+
+      -- ALGLIB --
+         Copyright 21.07.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void corrr1d(double[] signal, int n, double[] pattern, int m, out double[] r)
+    {
+        r = new double[0];
+        corr.corrr1d(signal, n, pattern, m, ref r);
+        return;
+    }
+
+    /*************************************************************************
+    1-dimensional circular real cross-correlation.
+
+    For given Pattern/Signal returns corr(Pattern,Signal) (circular).
+    Algorithm has linearithmic complexity for any M/N.
+
+    IMPORTANT:
+        for  historical reasons subroutine accepts its parameters in  reversed
+        order:   CorrR1DCircular(Signal, Pattern) = Pattern x Signal    (using
+        traditional definition of cross-correlation, denoting cross-correlation
+        as "x").
+
+    INPUT PARAMETERS
+        Signal  -   array[0..N-1] - real function to be transformed,
+                    periodic signal containing pattern
+        N       -   problem size
+        Pattern -   array[0..M-1] - real function to be transformed,
+                    non-periodic pattern to search withing signal
+        M       -   problem size
+
+    OUTPUT PARAMETERS
+        R   -   convolution: A*B. array[0..M-1].
+
+
+      -- ALGLIB --
+         Copyright 21.07.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void corrr1dcircular(double[] signal, int m, double[] pattern, int n, out double[] c)
+    {
+        c = new double[0];
+        corr.corrr1dcircular(signal, m, pattern, n, ref c);
+        return;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    1-dimensional Fast Hartley Transform.
+
+    Algorithm has O(N*logN) complexity for any N (composite or prime).
+
+    INPUT PARAMETERS
+        A   -   array[0..N-1] - real function to be transformed
+        N   -   problem size
+
+    OUTPUT PARAMETERS
+        A   -   FHT of a input array, array[0..N-1],
+                A_out[k] = sum(A_in[j]*(cos(2*pi*j*k/N)+sin(2*pi*j*k/N)), j=0..N-1)
+
+
+      -- ALGLIB --
+         Copyright 04.06.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void fhtr1d(ref double[] a, int n)
+    {
+
+        fht.fhtr1d(ref a, n);
+        return;
+    }
+
+    /*************************************************************************
+    1-dimensional inverse FHT.
+
+    Algorithm has O(N*logN) complexity for any N (composite or prime).
+
+    INPUT PARAMETERS
+        A   -   array[0..N-1] - complex array to be transformed
+        N   -   problem size
+
+    OUTPUT PARAMETERS
+        A   -   inverse FHT of a input array, array[0..N-1]
+
+
+      -- ALGLIB --
+         Copyright 29.05.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void fhtr1dinv(ref double[] a, int n)
+    {
+
+        fht.fhtr1dinv(ref a, n);
+        return;
+    }
+
+}
+public partial class alglib
+{
+    public class fft
+    {
+        /*************************************************************************
+        1-dimensional complex FFT.
+
+        Array size N may be arbitrary number (composite or prime).  Composite  N's
+        are handled with cache-oblivious variation of  a  Cooley-Tukey  algorithm.
+        Small prime-factors are transformed using hard coded  codelets (similar to
+        FFTW codelets, but without low-level  optimization),  large  prime-factors
+        are handled with Bluestein's algorithm.
+
+        Fastests transforms are for smooth N's (prime factors are 2, 3,  5  only),
+        most fast for powers of 2. When N have prime factors  larger  than  these,
+        but orders of magnitude smaller than N, computations will be about 4 times
+        slower than for nearby highly composite N's. When N itself is prime, speed
+        will be 6 times lower.
+
+        Algorithm has O(N*logN) complexity for any N (composite or prime).
+
+        INPUT PARAMETERS
+            A   -   array[0..N-1] - complex function to be transformed
+            N   -   problem size
+            
+        OUTPUT PARAMETERS
+            A   -   DFT of a input array, array[0..N-1]
+                    A_out[j] = SUM(A_in[k]*exp(-2*pi*sqrt(-1)*j*k/N), k = 0..N-1)
+
+
+          -- ALGLIB --
+             Copyright 29.05.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void fftc1d(ref complex[] a,
+            int n)
+        {
+            ftbase.ftplan plan = new ftbase.ftplan();
+            int i = 0;
+            double[] buf = new double[0];
+
+            alglib.ap.assert(n>0, "FFTC1D: incorrect N!");
+            alglib.ap.assert(alglib.ap.len(a)>=n, "FFTC1D: Length(A)<N!");
+            alglib.ap.assert(apserv.isfinitecvector(a, n), "FFTC1D: A contains infinite or NAN values!");
+            
+            //
+            // Special case: N=1, FFT is just identity transform.
+            // After this block we assume that N is strictly greater than 1.
+            //
+            if( n==1 )
+            {
+                return;
+            }
+            
+            //
+            // convert input array to the more convinient format
+            //
+            buf = new double[2*n];
+            for(i=0; i<=n-1; i++)
+            {
+                buf[2*i+0] = a[i].x;
+                buf[2*i+1] = a[i].y;
+            }
+            
+            //
+            // Generate plan and execute it.
+            //
+            // Plan is a combination of a successive factorizations of N and
+            // precomputed data. It is much like a FFTW plan, but is not stored
+            // between subroutine calls and is much simpler.
+            //
+            ftbase.ftbasegeneratecomplexfftplan(n, plan);
+            ftbase.ftbaseexecuteplan(ref buf, 0, n, plan);
+            
+            //
+            // result
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                a[i].x = buf[2*i+0];
+                a[i].y = buf[2*i+1];
+            }
+        }
+
+
+        /*************************************************************************
+        1-dimensional complex inverse FFT.
+
+        Array size N may be arbitrary number (composite or prime).  Algorithm  has
+        O(N*logN) complexity for any N (composite or prime).
+
+        See FFTC1D() description for more information about algorithm performance.
+
+        INPUT PARAMETERS
+            A   -   array[0..N-1] - complex array to be transformed
+            N   -   problem size
+
+        OUTPUT PARAMETERS
+            A   -   inverse DFT of a input array, array[0..N-1]
+                    A_out[j] = SUM(A_in[k]/N*exp(+2*pi*sqrt(-1)*j*k/N), k = 0..N-1)
+
+
+          -- ALGLIB --
+             Copyright 29.05.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void fftc1dinv(ref complex[] a,
+            int n)
+        {
+            int i = 0;
+
+            alglib.ap.assert(n>0, "FFTC1DInv: incorrect N!");
+            alglib.ap.assert(alglib.ap.len(a)>=n, "FFTC1DInv: Length(A)<N!");
+            alglib.ap.assert(apserv.isfinitecvector(a, n), "FFTC1DInv: A contains infinite or NAN values!");
+            
+            //
+            // Inverse DFT can be expressed in terms of the DFT as
+            //
+            //     invfft(x) = fft(x')'/N
+            //
+            // here x' means conj(x).
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                a[i].y = -a[i].y;
+            }
+            fftc1d(ref a, n);
+            for(i=0; i<=n-1; i++)
+            {
+                a[i].x = a[i].x/n;
+                a[i].y = -(a[i].y/n);
+            }
+        }
+
+
+        /*************************************************************************
+        1-dimensional real FFT.
+
+        Algorithm has O(N*logN) complexity for any N (composite or prime).
+
+        INPUT PARAMETERS
+            A   -   array[0..N-1] - real function to be transformed
+            N   -   problem size
+
+        OUTPUT PARAMETERS
+            F   -   DFT of a input array, array[0..N-1]
+                    F[j] = SUM(A[k]*exp(-2*pi*sqrt(-1)*j*k/N), k = 0..N-1)
+
+        NOTE:
+            F[] satisfies symmetry property F[k] = conj(F[N-k]),  so just one half
+        of  array  is  usually needed. But for convinience subroutine returns full
+        complex array (with frequencies above N/2), so its result may be  used  by
+        other FFT-related subroutines.
+
+
+          -- ALGLIB --
+             Copyright 01.06.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void fftr1d(double[] a,
+            int n,
+            ref complex[] f)
+        {
+            int i = 0;
+            int n2 = 0;
+            int idx = 0;
+            complex hn = 0;
+            complex hmnc = 0;
+            complex v = 0;
+            double[] buf = new double[0];
+            ftbase.ftplan plan = new ftbase.ftplan();
+            int i_ = 0;
+
+            f = new complex[0];
+
+            alglib.ap.assert(n>0, "FFTR1D: incorrect N!");
+            alglib.ap.assert(alglib.ap.len(a)>=n, "FFTR1D: Length(A)<N!");
+            alglib.ap.assert(apserv.isfinitevector(a, n), "FFTR1D: A contains infinite or NAN values!");
+            
+            //
+            // Special cases:
+            // * N=1, FFT is just identity transform.
+            // * N=2, FFT is simple too
+            //
+            // After this block we assume that N is strictly greater than 2
+            //
+            if( n==1 )
+            {
+                f = new complex[1];
+                f[0] = a[0];
+                return;
+            }
+            if( n==2 )
+            {
+                f = new complex[2];
+                f[0].x = a[0]+a[1];
+                f[0].y = 0;
+                f[1].x = a[0]-a[1];
+                f[1].y = 0;
+                return;
+            }
+            
+            //
+            // Choose between odd-size and even-size FFTs
+            //
+            if( n%2==0 )
+            {
+                
+                //
+                // even-size real FFT, use reduction to the complex task
+                //
+                n2 = n/2;
+                buf = new double[n];
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    buf[i_] = a[i_];
+                }
+                ftbase.ftbasegeneratecomplexfftplan(n2, plan);
+                ftbase.ftbaseexecuteplan(ref buf, 0, n2, plan);
+                f = new complex[n];
+                for(i=0; i<=n2; i++)
+                {
+                    idx = 2*(i%n2);
+                    hn.x = buf[idx+0];
+                    hn.y = buf[idx+1];
+                    idx = 2*((n2-i)%n2);
+                    hmnc.x = buf[idx+0];
+                    hmnc.y = -buf[idx+1];
+                    v.x = -Math.Sin(-(2*Math.PI*i/n));
+                    v.y = Math.Cos(-(2*Math.PI*i/n));
+                    f[i] = hn+hmnc-v*(hn-hmnc);
+                    f[i].x = 0.5*f[i].x;
+                    f[i].y = 0.5*f[i].y;
+                }
+                for(i=n2+1; i<=n-1; i++)
+                {
+                    f[i] = math.conj(f[n-i]);
+                }
+            }
+            else
+            {
+                
+                //
+                // use complex FFT
+                //
+                f = new complex[n];
+                for(i=0; i<=n-1; i++)
+                {
+                    f[i] = a[i];
+                }
+                fftc1d(ref f, n);
+            }
+        }
+
+
+        /*************************************************************************
+        1-dimensional real inverse FFT.
+
+        Algorithm has O(N*logN) complexity for any N (composite or prime).
+
+        INPUT PARAMETERS
+            F   -   array[0..floor(N/2)] - frequencies from forward real FFT
+            N   -   problem size
+
+        OUTPUT PARAMETERS
+            A   -   inverse DFT of a input array, array[0..N-1]
+
+        NOTE:
+            F[] should satisfy symmetry property F[k] = conj(F[N-k]), so just  one
+        half of frequencies array is needed - elements from 0 to floor(N/2).  F[0]
+        is ALWAYS real. If N is even F[floor(N/2)] is real too. If N is odd,  then
+        F[floor(N/2)] has no special properties.
+
+        Relying on properties noted above, FFTR1DInv subroutine uses only elements
+        from 0th to floor(N/2)-th. It ignores imaginary part of F[0],  and in case
+        N is even it ignores imaginary part of F[floor(N/2)] too.
+
+        When you call this function using full arguments list - "FFTR1DInv(F,N,A)"
+        - you can pass either either frequencies array with N elements or  reduced
+        array with roughly N/2 elements - subroutine will  successfully  transform
+        both.
+
+        If you call this function using reduced arguments list -  "FFTR1DInv(F,A)"
+        - you must pass FULL array with N elements (although higher  N/2 are still
+        not used) because array size is used to automatically determine FFT length
+
+
+          -- ALGLIB --
+             Copyright 01.06.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void fftr1dinv(complex[] f,
+            int n,
+            ref double[] a)
+        {
+            int i = 0;
+            double[] h = new double[0];
+            complex[] fh = new complex[0];
+
+            a = new double[0];
+
+            alglib.ap.assert(n>0, "FFTR1DInv: incorrect N!");
+            alglib.ap.assert(alglib.ap.len(f)>=(int)Math.Floor((double)n/(double)2)+1, "FFTR1DInv: Length(F)<Floor(N/2)+1!");
+            alglib.ap.assert(math.isfinite(f[0].x), "FFTR1DInv: F contains infinite or NAN values!");
+            for(i=1; i<=(int)Math.Floor((double)n/(double)2)-1; i++)
+            {
+                alglib.ap.assert(math.isfinite(f[i].x) && math.isfinite(f[i].y), "FFTR1DInv: F contains infinite or NAN values!");
+            }
+            alglib.ap.assert(math.isfinite(f[(int)Math.Floor((double)n/(double)2)].x), "FFTR1DInv: F contains infinite or NAN values!");
+            if( n%2!=0 )
+            {
+                alglib.ap.assert(math.isfinite(f[(int)Math.Floor((double)n/(double)2)].y), "FFTR1DInv: F contains infinite or NAN values!");
+            }
+            
+            //
+            // Special case: N=1, FFT is just identity transform.
+            // After this block we assume that N is strictly greater than 1.
+            //
+            if( n==1 )
+            {
+                a = new double[1];
+                a[0] = f[0].x;
+                return;
+            }
+            
+            //
+            // inverse real FFT is reduced to the inverse real FHT,
+            // which is reduced to the forward real FHT,
+            // which is reduced to the forward real FFT.
+            //
+            // Don't worry, it is really compact and efficient reduction :)
+            //
+            h = new double[n];
+            a = new double[n];
+            h[0] = f[0].x;
+            for(i=1; i<=(int)Math.Floor((double)n/(double)2)-1; i++)
+            {
+                h[i] = f[i].x-f[i].y;
+                h[n-i] = f[i].x+f[i].y;
+            }
+            if( n%2==0 )
+            {
+                h[(int)Math.Floor((double)n/(double)2)] = f[(int)Math.Floor((double)n/(double)2)].x;
+            }
+            else
+            {
+                h[(int)Math.Floor((double)n/(double)2)] = f[(int)Math.Floor((double)n/(double)2)].x-f[(int)Math.Floor((double)n/(double)2)].y;
+                h[(int)Math.Floor((double)n/(double)2)+1] = f[(int)Math.Floor((double)n/(double)2)].x+f[(int)Math.Floor((double)n/(double)2)].y;
+            }
+            fftr1d(h, n, ref fh);
+            for(i=0; i<=n-1; i++)
+            {
+                a[i] = (fh[i].x-fh[i].y)/n;
+            }
+        }
+
+
+        /*************************************************************************
+        Internal subroutine. Never call it directly!
+
+
+          -- ALGLIB --
+             Copyright 01.06.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void fftr1dinternaleven(ref double[] a,
+            int n,
+            ref double[] buf,
+            ftbase.ftplan plan)
+        {
+            double x = 0;
+            double y = 0;
+            int i = 0;
+            int n2 = 0;
+            int idx = 0;
+            complex hn = 0;
+            complex hmnc = 0;
+            complex v = 0;
+            int i_ = 0;
+
+            alglib.ap.assert(n>0 && n%2==0, "FFTR1DEvenInplace: incorrect N!");
+            
+            //
+            // Special cases:
+            // * N=2
+            //
+            // After this block we assume that N is strictly greater than 2
+            //
+            if( n==2 )
+            {
+                x = a[0]+a[1];
+                y = a[0]-a[1];
+                a[0] = x;
+                a[1] = y;
+                return;
+            }
+            
+            //
+            // even-size real FFT, use reduction to the complex task
+            //
+            n2 = n/2;
+            for(i_=0; i_<=n-1;i_++)
+            {
+                buf[i_] = a[i_];
+            }
+            ftbase.ftbaseexecuteplan(ref buf, 0, n2, plan);
+            a[0] = buf[0]+buf[1];
+            for(i=1; i<=n2-1; i++)
+            {
+                idx = 2*(i%n2);
+                hn.x = buf[idx+0];
+                hn.y = buf[idx+1];
+                idx = 2*(n2-i);
+                hmnc.x = buf[idx+0];
+                hmnc.y = -buf[idx+1];
+                v.x = -Math.Sin(-(2*Math.PI*i/n));
+                v.y = Math.Cos(-(2*Math.PI*i/n));
+                v = hn+hmnc-v*(hn-hmnc);
+                a[2*i+0] = 0.5*v.x;
+                a[2*i+1] = 0.5*v.y;
+            }
+            a[1] = buf[0]-buf[1];
+        }
+
+
+        /*************************************************************************
+        Internal subroutine. Never call it directly!
+
+
+          -- ALGLIB --
+             Copyright 01.06.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void fftr1dinvinternaleven(ref double[] a,
+            int n,
+            ref double[] buf,
+            ftbase.ftplan plan)
+        {
+            double x = 0;
+            double y = 0;
+            double t = 0;
+            int i = 0;
+            int n2 = 0;
+
+            alglib.ap.assert(n>0 && n%2==0, "FFTR1DInvInternalEven: incorrect N!");
+            
+            //
+            // Special cases:
+            // * N=2
+            //
+            // After this block we assume that N is strictly greater than 2
+            //
+            if( n==2 )
+            {
+                x = 0.5*(a[0]+a[1]);
+                y = 0.5*(a[0]-a[1]);
+                a[0] = x;
+                a[1] = y;
+                return;
+            }
+            
+            //
+            // inverse real FFT is reduced to the inverse real FHT,
+            // which is reduced to the forward real FHT,
+            // which is reduced to the forward real FFT.
+            //
+            // Don't worry, it is really compact and efficient reduction :)
+            //
+            n2 = n/2;
+            buf[0] = a[0];
+            for(i=1; i<=n2-1; i++)
+            {
+                x = a[2*i+0];
+                y = a[2*i+1];
+                buf[i] = x-y;
+                buf[n-i] = x+y;
+            }
+            buf[n2] = a[1];
+            fftr1dinternaleven(ref buf, n, ref a, plan);
+            a[0] = buf[0]/n;
+            t = (double)1/(double)n;
+            for(i=1; i<=n2-1; i++)
+            {
+                x = buf[2*i+0];
+                y = buf[2*i+1];
+                a[i] = t*(x-y);
+                a[n-i] = t*(x+y);
+            }
+            a[n2] = buf[1]/n;
+        }
+
+
+    }
+    public class conv
+    {
+        /*************************************************************************
+        1-dimensional complex convolution.
+
+        For given A/B returns conv(A,B) (non-circular). Subroutine can automatically
+        choose between three implementations: straightforward O(M*N)  formula  for
+        very small N (or M), overlap-add algorithm for  cases  where  max(M,N)  is
+        significantly larger than min(M,N), but O(M*N) algorithm is too slow,  and
+        general FFT-based formula for cases where two previois algorithms are  too
+        slow.
+
+        Algorithm has max(M,N)*log(max(M,N)) complexity for any M/N.
+
+        INPUT PARAMETERS
+            A   -   array[0..M-1] - complex function to be transformed
+            M   -   problem size
+            B   -   array[0..N-1] - complex function to be transformed
+            N   -   problem size
+
+        OUTPUT PARAMETERS
+            R   -   convolution: A*B. array[0..N+M-2].
+
+        NOTE:
+            It is assumed that A is zero at T<0, B is zero too.  If  one  or  both
+        functions have non-zero values at negative T's, you  can  still  use  this
+        subroutine - just shift its result correspondingly.
+
+          -- ALGLIB --
+             Copyright 21.07.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void convc1d(complex[] a,
+            int m,
+            complex[] b,
+            int n,
+            ref complex[] r)
+        {
+            r = new complex[0];
+
+            alglib.ap.assert(n>0 && m>0, "ConvC1D: incorrect N or M!");
+            
+            //
+            // normalize task: make M>=N,
+            // so A will be longer that B.
+            //
+            if( m<n )
+            {
+                convc1d(b, n, a, m, ref r);
+                return;
+            }
+            convc1dx(a, m, b, n, false, -1, 0, ref r);
+        }
+
+
+        /*************************************************************************
+        1-dimensional complex non-circular deconvolution (inverse of ConvC1D()).
+
+        Algorithm has M*log(M)) complexity for any M (composite or prime).
+
+        INPUT PARAMETERS
+            A   -   array[0..M-1] - convolved signal, A = conv(R, B)
+            M   -   convolved signal length
+            B   -   array[0..N-1] - response
+            N   -   response length, N<=M
+
+        OUTPUT PARAMETERS
+            R   -   deconvolved signal. array[0..M-N].
+
+        NOTE:
+            deconvolution is unstable process and may result in division  by  zero
+        (if your response function is degenerate, i.e. has zero Fourier coefficient).
+
+        NOTE:
+            It is assumed that A is zero at T<0, B is zero too.  If  one  or  both
+        functions have non-zero values at negative T's, you  can  still  use  this
+        subroutine - just shift its result correspondingly.
+
+          -- ALGLIB --
+             Copyright 21.07.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void convc1dinv(complex[] a,
+            int m,
+            complex[] b,
+            int n,
+            ref complex[] r)
+        {
+            int i = 0;
+            int p = 0;
+            double[] buf = new double[0];
+            double[] buf2 = new double[0];
+            ftbase.ftplan plan = new ftbase.ftplan();
+            complex c1 = 0;
+            complex c2 = 0;
+            complex c3 = 0;
+            double t = 0;
+
+            r = new complex[0];
+
+            alglib.ap.assert((n>0 && m>0) && n<=m, "ConvC1DInv: incorrect N or M!");
+            p = ftbase.ftbasefindsmooth(m);
+            ftbase.ftbasegeneratecomplexfftplan(p, plan);
+            buf = new double[2*p];
+            for(i=0; i<=m-1; i++)
+            {
+                buf[2*i+0] = a[i].x;
+                buf[2*i+1] = a[i].y;
+            }
+            for(i=m; i<=p-1; i++)
+            {
+                buf[2*i+0] = 0;
+                buf[2*i+1] = 0;
+            }
+            buf2 = new double[2*p];
+            for(i=0; i<=n-1; i++)
+            {
+                buf2[2*i+0] = b[i].x;
+                buf2[2*i+1] = b[i].y;
+            }
+            for(i=n; i<=p-1; i++)
+            {
+                buf2[2*i+0] = 0;
+                buf2[2*i+1] = 0;
+            }
+            ftbase.ftbaseexecuteplan(ref buf, 0, p, plan);
+            ftbase.ftbaseexecuteplan(ref buf2, 0, p, plan);
+            for(i=0; i<=p-1; i++)
+            {
+                c1.x = buf[2*i+0];
+                c1.y = buf[2*i+1];
+                c2.x = buf2[2*i+0];
+                c2.y = buf2[2*i+1];
+                c3 = c1/c2;
+                buf[2*i+0] = c3.x;
+                buf[2*i+1] = -c3.y;
+            }
+            ftbase.ftbaseexecuteplan(ref buf, 0, p, plan);
+            t = (double)1/(double)p;
+            r = new complex[m-n+1];
+            for(i=0; i<=m-n; i++)
+            {
+                r[i].x = t*buf[2*i+0];
+                r[i].y = -(t*buf[2*i+1]);
+            }
+        }
+
+
+        /*************************************************************************
+        1-dimensional circular complex convolution.
+
+        For given S/R returns conv(S,R) (circular). Algorithm has linearithmic
+        complexity for any M/N.
+
+        IMPORTANT:  normal convolution is commutative,  i.e.   it  is symmetric  -
+        conv(A,B)=conv(B,A).  Cyclic convolution IS NOT.  One function - S - is  a
+        signal,  periodic function, and another - R - is a response,  non-periodic
+        function with limited length.
+
+        INPUT PARAMETERS
+            S   -   array[0..M-1] - complex periodic signal
+            M   -   problem size
+            B   -   array[0..N-1] - complex non-periodic response
+            N   -   problem size
+
+        OUTPUT PARAMETERS
+            R   -   convolution: A*B. array[0..M-1].
+
+        NOTE:
+            It is assumed that B is zero at T<0. If  it  has  non-zero  values  at
+        negative T's, you can still use this subroutine - just  shift  its  result
+        correspondingly.
+
+          -- ALGLIB --
+             Copyright 21.07.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void convc1dcircular(complex[] s,
+            int m,
+            complex[] r,
+            int n,
+            ref complex[] c)
+        {
+            complex[] buf = new complex[0];
+            int i1 = 0;
+            int i2 = 0;
+            int j2 = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            c = new complex[0];
+
+            alglib.ap.assert(n>0 && m>0, "ConvC1DCircular: incorrect N or M!");
+            
+            //
+            // normalize task: make M>=N,
+            // so A will be longer (at least - not shorter) that B.
+            //
+            if( m<n )
+            {
+                buf = new complex[m];
+                for(i1=0; i1<=m-1; i1++)
+                {
+                    buf[i1] = 0;
+                }
+                i1 = 0;
+                while( i1<n )
+                {
+                    i2 = Math.Min(i1+m-1, n-1);
+                    j2 = i2-i1;
+                    i1_ = (i1) - (0);
+                    for(i_=0; i_<=j2;i_++)
+                    {
+                        buf[i_] = buf[i_] + r[i_+i1_];
+                    }
+                    i1 = i1+m;
+                }
+                convc1dcircular(s, m, buf, m, ref c);
+                return;
+            }
+            convc1dx(s, m, r, n, true, -1, 0, ref c);
+        }
+
+
+        /*************************************************************************
+        1-dimensional circular complex deconvolution (inverse of ConvC1DCircular()).
+
+        Algorithm has M*log(M)) complexity for any M (composite or prime).
+
+        INPUT PARAMETERS
+            A   -   array[0..M-1] - convolved periodic signal, A = conv(R, B)
+            M   -   convolved signal length
+            B   -   array[0..N-1] - non-periodic response
+            N   -   response length
+
+        OUTPUT PARAMETERS
+            R   -   deconvolved signal. array[0..M-1].
+
+        NOTE:
+            deconvolution is unstable process and may result in division  by  zero
+        (if your response function is degenerate, i.e. has zero Fourier coefficient).
+
+        NOTE:
+            It is assumed that B is zero at T<0. If  it  has  non-zero  values  at
+        negative T's, you can still use this subroutine - just  shift  its  result
+        correspondingly.
+
+          -- ALGLIB --
+             Copyright 21.07.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void convc1dcircularinv(complex[] a,
+            int m,
+            complex[] b,
+            int n,
+            ref complex[] r)
+        {
+            int i = 0;
+            int i1 = 0;
+            int i2 = 0;
+            int j2 = 0;
+            double[] buf = new double[0];
+            double[] buf2 = new double[0];
+            complex[] cbuf = new complex[0];
+            ftbase.ftplan plan = new ftbase.ftplan();
+            complex c1 = 0;
+            complex c2 = 0;
+            complex c3 = 0;
+            double t = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            r = new complex[0];
+
+            alglib.ap.assert(n>0 && m>0, "ConvC1DCircularInv: incorrect N or M!");
+            
+            //
+            // normalize task: make M>=N,
+            // so A will be longer (at least - not shorter) that B.
+            //
+            if( m<n )
+            {
+                cbuf = new complex[m];
+                for(i=0; i<=m-1; i++)
+                {
+                    cbuf[i] = 0;
+                }
+                i1 = 0;
+                while( i1<n )
+                {
+                    i2 = Math.Min(i1+m-1, n-1);
+                    j2 = i2-i1;
+                    i1_ = (i1) - (0);
+                    for(i_=0; i_<=j2;i_++)
+                    {
+                        cbuf[i_] = cbuf[i_] + b[i_+i1_];
+                    }
+                    i1 = i1+m;
+                }
+                convc1dcircularinv(a, m, cbuf, m, ref r);
+                return;
+            }
+            
+            //
+            // Task is normalized
+            //
+            ftbase.ftbasegeneratecomplexfftplan(m, plan);
+            buf = new double[2*m];
+            for(i=0; i<=m-1; i++)
+            {
+                buf[2*i+0] = a[i].x;
+                buf[2*i+1] = a[i].y;
+            }
+            buf2 = new double[2*m];
+            for(i=0; i<=n-1; i++)
+            {
+                buf2[2*i+0] = b[i].x;
+                buf2[2*i+1] = b[i].y;
+            }
+            for(i=n; i<=m-1; i++)
+            {
+                buf2[2*i+0] = 0;
+                buf2[2*i+1] = 0;
+            }
+            ftbase.ftbaseexecuteplan(ref buf, 0, m, plan);
+            ftbase.ftbaseexecuteplan(ref buf2, 0, m, plan);
+            for(i=0; i<=m-1; i++)
+            {
+                c1.x = buf[2*i+0];
+                c1.y = buf[2*i+1];
+                c2.x = buf2[2*i+0];
+                c2.y = buf2[2*i+1];
+                c3 = c1/c2;
+                buf[2*i+0] = c3.x;
+                buf[2*i+1] = -c3.y;
+            }
+            ftbase.ftbaseexecuteplan(ref buf, 0, m, plan);
+            t = (double)1/(double)m;
+            r = new complex[m];
+            for(i=0; i<=m-1; i++)
+            {
+                r[i].x = t*buf[2*i+0];
+                r[i].y = -(t*buf[2*i+1]);
+            }
+        }
+
+
+        /*************************************************************************
+        1-dimensional real convolution.
+
+        Analogous to ConvC1D(), see ConvC1D() comments for more details.
+
+        INPUT PARAMETERS
+            A   -   array[0..M-1] - real function to be transformed
+            M   -   problem size
+            B   -   array[0..N-1] - real function to be transformed
+            N   -   problem size
+
+        OUTPUT PARAMETERS
+            R   -   convolution: A*B. array[0..N+M-2].
+
+        NOTE:
+            It is assumed that A is zero at T<0, B is zero too.  If  one  or  both
+        functions have non-zero values at negative T's, you  can  still  use  this
+        subroutine - just shift its result correspondingly.
+
+          -- ALGLIB --
+             Copyright 21.07.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void convr1d(double[] a,
+            int m,
+            double[] b,
+            int n,
+            ref double[] r)
+        {
+            r = new double[0];
+
+            alglib.ap.assert(n>0 && m>0, "ConvR1D: incorrect N or M!");
+            
+            //
+            // normalize task: make M>=N,
+            // so A will be longer that B.
+            //
+            if( m<n )
+            {
+                convr1d(b, n, a, m, ref r);
+                return;
+            }
+            convr1dx(a, m, b, n, false, -1, 0, ref r);
+        }
+
+
+        /*************************************************************************
+        1-dimensional real deconvolution (inverse of ConvC1D()).
+
+        Algorithm has M*log(M)) complexity for any M (composite or prime).
+
+        INPUT PARAMETERS
+            A   -   array[0..M-1] - convolved signal, A = conv(R, B)
+            M   -   convolved signal length
+            B   -   array[0..N-1] - response
+            N   -   response length, N<=M
+
+        OUTPUT PARAMETERS
+            R   -   deconvolved signal. array[0..M-N].
+
+        NOTE:
+            deconvolution is unstable process and may result in division  by  zero
+        (if your response function is degenerate, i.e. has zero Fourier coefficient).
+
+        NOTE:
+            It is assumed that A is zero at T<0, B is zero too.  If  one  or  both
+        functions have non-zero values at negative T's, you  can  still  use  this
+        subroutine - just shift its result correspondingly.
+
+          -- ALGLIB --
+             Copyright 21.07.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void convr1dinv(double[] a,
+            int m,
+            double[] b,
+            int n,
+            ref double[] r)
+        {
+            int i = 0;
+            int p = 0;
+            double[] buf = new double[0];
+            double[] buf2 = new double[0];
+            double[] buf3 = new double[0];
+            ftbase.ftplan plan = new ftbase.ftplan();
+            complex c1 = 0;
+            complex c2 = 0;
+            complex c3 = 0;
+            int i_ = 0;
+
+            r = new double[0];
+
+            alglib.ap.assert((n>0 && m>0) && n<=m, "ConvR1DInv: incorrect N or M!");
+            p = ftbase.ftbasefindsmootheven(m);
+            buf = new double[p];
+            for(i_=0; i_<=m-1;i_++)
+            {
+                buf[i_] = a[i_];
+            }
+            for(i=m; i<=p-1; i++)
+            {
+                buf[i] = 0;
+            }
+            buf2 = new double[p];
+            for(i_=0; i_<=n-1;i_++)
+            {
+                buf2[i_] = b[i_];
+            }
+            for(i=n; i<=p-1; i++)
+            {
+                buf2[i] = 0;
+            }
+            buf3 = new double[p];
+            ftbase.ftbasegeneratecomplexfftplan(p/2, plan);
+            fft.fftr1dinternaleven(ref buf, p, ref buf3, plan);
+            fft.fftr1dinternaleven(ref buf2, p, ref buf3, plan);
+            buf[0] = buf[0]/buf2[0];
+            buf[1] = buf[1]/buf2[1];
+            for(i=1; i<=p/2-1; i++)
+            {
+                c1.x = buf[2*i+0];
+                c1.y = buf[2*i+1];
+                c2.x = buf2[2*i+0];
+                c2.y = buf2[2*i+1];
+                c3 = c1/c2;
+                buf[2*i+0] = c3.x;
+                buf[2*i+1] = c3.y;
+            }
+            fft.fftr1dinvinternaleven(ref buf, p, ref buf3, plan);
+            r = new double[m-n+1];
+            for(i_=0; i_<=m-n;i_++)
+            {
+                r[i_] = buf[i_];
+            }
+        }
+
+
+        /*************************************************************************
+        1-dimensional circular real convolution.
+
+        Analogous to ConvC1DCircular(), see ConvC1DCircular() comments for more details.
+
+        INPUT PARAMETERS
+            S   -   array[0..M-1] - real signal
+            M   -   problem size
+            B   -   array[0..N-1] - real response
+            N   -   problem size
+
+        OUTPUT PARAMETERS
+            R   -   convolution: A*B. array[0..M-1].
+
+        NOTE:
+            It is assumed that B is zero at T<0. If  it  has  non-zero  values  at
+        negative T's, you can still use this subroutine - just  shift  its  result
+        correspondingly.
+
+          -- ALGLIB --
+             Copyright 21.07.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void convr1dcircular(double[] s,
+            int m,
+            double[] r,
+            int n,
+            ref double[] c)
+        {
+            double[] buf = new double[0];
+            int i1 = 0;
+            int i2 = 0;
+            int j2 = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            c = new double[0];
+
+            alglib.ap.assert(n>0 && m>0, "ConvC1DCircular: incorrect N or M!");
+            
+            //
+            // normalize task: make M>=N,
+            // so A will be longer (at least - not shorter) that B.
+            //
+            if( m<n )
+            {
+                buf = new double[m];
+                for(i1=0; i1<=m-1; i1++)
+                {
+                    buf[i1] = 0;
+                }
+                i1 = 0;
+                while( i1<n )
+                {
+                    i2 = Math.Min(i1+m-1, n-1);
+                    j2 = i2-i1;
+                    i1_ = (i1) - (0);
+                    for(i_=0; i_<=j2;i_++)
+                    {
+                        buf[i_] = buf[i_] + r[i_+i1_];
+                    }
+                    i1 = i1+m;
+                }
+                convr1dcircular(s, m, buf, m, ref c);
+                return;
+            }
+            
+            //
+            // reduce to usual convolution
+            //
+            convr1dx(s, m, r, n, true, -1, 0, ref c);
+        }
+
+
+        /*************************************************************************
+        1-dimensional complex deconvolution (inverse of ConvC1D()).
+
+        Algorithm has M*log(M)) complexity for any M (composite or prime).
+
+        INPUT PARAMETERS
+            A   -   array[0..M-1] - convolved signal, A = conv(R, B)
+            M   -   convolved signal length
+            B   -   array[0..N-1] - response
+            N   -   response length
+
+        OUTPUT PARAMETERS
+            R   -   deconvolved signal. array[0..M-N].
+
+        NOTE:
+            deconvolution is unstable process and may result in division  by  zero
+        (if your response function is degenerate, i.e. has zero Fourier coefficient).
+
+        NOTE:
+            It is assumed that B is zero at T<0. If  it  has  non-zero  values  at
+        negative T's, you can still use this subroutine - just  shift  its  result
+        correspondingly.
+
+          -- ALGLIB --
+             Copyright 21.07.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void convr1dcircularinv(double[] a,
+            int m,
+            double[] b,
+            int n,
+            ref double[] r)
+        {
+            int i = 0;
+            int i1 = 0;
+            int i2 = 0;
+            int j2 = 0;
+            double[] buf = new double[0];
+            double[] buf2 = new double[0];
+            double[] buf3 = new double[0];
+            complex[] cbuf = new complex[0];
+            complex[] cbuf2 = new complex[0];
+            ftbase.ftplan plan = new ftbase.ftplan();
+            complex c1 = 0;
+            complex c2 = 0;
+            complex c3 = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            r = new double[0];
+
+            alglib.ap.assert(n>0 && m>0, "ConvR1DCircularInv: incorrect N or M!");
+            
+            //
+            // normalize task: make M>=N,
+            // so A will be longer (at least - not shorter) that B.
+            //
+            if( m<n )
+            {
+                buf = new double[m];
+                for(i=0; i<=m-1; i++)
+                {
+                    buf[i] = 0;
+                }
+                i1 = 0;
+                while( i1<n )
+                {
+                    i2 = Math.Min(i1+m-1, n-1);
+                    j2 = i2-i1;
+                    i1_ = (i1) - (0);
+                    for(i_=0; i_<=j2;i_++)
+                    {
+                        buf[i_] = buf[i_] + b[i_+i1_];
+                    }
+                    i1 = i1+m;
+                }
+                convr1dcircularinv(a, m, buf, m, ref r);
+                return;
+            }
+            
+            //
+            // Task is normalized
+            //
+            if( m%2==0 )
+            {
+                
+                //
+                // size is even, use fast even-size FFT
+                //
+                buf = new double[m];
+                for(i_=0; i_<=m-1;i_++)
+                {
+                    buf[i_] = a[i_];
+                }
+                buf2 = new double[m];
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    buf2[i_] = b[i_];
+                }
+                for(i=n; i<=m-1; i++)
+                {
+                    buf2[i] = 0;
+                }
+                buf3 = new double[m];
+                ftbase.ftbasegeneratecomplexfftplan(m/2, plan);
+                fft.fftr1dinternaleven(ref buf, m, ref buf3, plan);
+                fft.fftr1dinternaleven(ref buf2, m, ref buf3, plan);
+                buf[0] = buf[0]/buf2[0];
+                buf[1] = buf[1]/buf2[1];
+                for(i=1; i<=m/2-1; i++)
+                {
+                    c1.x = buf[2*i+0];
+                    c1.y = buf[2*i+1];
+                    c2.x = buf2[2*i+0];
+                    c2.y = buf2[2*i+1];
+                    c3 = c1/c2;
+                    buf[2*i+0] = c3.x;
+                    buf[2*i+1] = c3.y;
+                }
+                fft.fftr1dinvinternaleven(ref buf, m, ref buf3, plan);
+                r = new double[m];
+                for(i_=0; i_<=m-1;i_++)
+                {
+                    r[i_] = buf[i_];
+                }
+            }
+            else
+            {
+                
+                //
+                // odd-size, use general real FFT
+                //
+                fft.fftr1d(a, m, ref cbuf);
+                buf2 = new double[m];
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    buf2[i_] = b[i_];
+                }
+                for(i=n; i<=m-1; i++)
+                {
+                    buf2[i] = 0;
+                }
+                fft.fftr1d(buf2, m, ref cbuf2);
+                for(i=0; i<=(int)Math.Floor((double)m/(double)2); i++)
+                {
+                    cbuf[i] = cbuf[i]/cbuf2[i];
+                }
+                fft.fftr1dinv(cbuf, m, ref r);
+            }
+        }
+
+
+        /*************************************************************************
+        1-dimensional complex convolution.
+
+        Extended subroutine which allows to choose convolution algorithm.
+        Intended for internal use, ALGLIB users should call ConvC1D()/ConvC1DCircular().
+
+        INPUT PARAMETERS
+            A   -   array[0..M-1] - complex function to be transformed
+            M   -   problem size
+            B   -   array[0..N-1] - complex function to be transformed
+            N   -   problem size, N<=M
+            Alg -   algorithm type:
+                    *-2     auto-select Q for overlap-add
+                    *-1     auto-select algorithm and parameters
+                    * 0     straightforward formula for small N's
+                    * 1     general FFT-based code
+                    * 2     overlap-add with length Q
+            Q   -   length for overlap-add
+
+        OUTPUT PARAMETERS
+            R   -   convolution: A*B. array[0..N+M-1].
+
+          -- ALGLIB --
+             Copyright 21.07.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void convc1dx(complex[] a,
+            int m,
+            complex[] b,
+            int n,
+            bool circular,
+            int alg,
+            int q,
+            ref complex[] r)
+        {
+            int i = 0;
+            int j = 0;
+            int p = 0;
+            int ptotal = 0;
+            int i1 = 0;
+            int i2 = 0;
+            int j1 = 0;
+            int j2 = 0;
+            complex[] bbuf = new complex[0];
+            complex v = 0;
+            double ax = 0;
+            double ay = 0;
+            double bx = 0;
+            double by = 0;
+            double t = 0;
+            double tx = 0;
+            double ty = 0;
+            double flopcand = 0;
+            double flopbest = 0;
+            int algbest = 0;
+            ftbase.ftplan plan = new ftbase.ftplan();
+            double[] buf = new double[0];
+            double[] buf2 = new double[0];
+            int i_ = 0;
+            int i1_ = 0;
+
+            r = new complex[0];
+
+            alglib.ap.assert(n>0 && m>0, "ConvC1DX: incorrect N or M!");
+            alglib.ap.assert(n<=m, "ConvC1DX: N<M assumption is false!");
+            
+            //
+            // Auto-select
+            //
+            if( alg==-1 || alg==-2 )
+            {
+                
+                //
+                // Initial candidate: straightforward implementation.
+                //
+                // If we want to use auto-fitted overlap-add,
+                // flop count is initialized by large real number - to force
+                // another algorithm selection
+                //
+                algbest = 0;
+                if( alg==-1 )
+                {
+                    flopbest = 2*m*n;
+                }
+                else
+                {
+                    flopbest = math.maxrealnumber;
+                }
+                
+                //
+                // Another candidate - generic FFT code
+                //
+                if( alg==-1 )
+                {
+                    if( circular && ftbase.ftbaseissmooth(m) )
+                    {
+                        
+                        //
+                        // special code for circular convolution of a sequence with a smooth length
+                        //
+                        flopcand = 3*ftbase.ftbasegetflopestimate(m)+6*m;
+                        if( (double)(flopcand)<(double)(flopbest) )
+                        {
+                            algbest = 1;
+                            flopbest = flopcand;
+                        }
+                    }
+                    else
+                    {
+                        
+                        //
+                        // general cyclic/non-cyclic convolution
+                        //
+                        p = ftbase.ftbasefindsmooth(m+n-1);
+                        flopcand = 3*ftbase.ftbasegetflopestimate(p)+6*p;
+                        if( (double)(flopcand)<(double)(flopbest) )
+                        {
+                            algbest = 1;
+                            flopbest = flopcand;
+                        }
+                    }
+                }
+                
+                //
+                // Another candidate - overlap-add
+                //
+                q = 1;
+                ptotal = 1;
+                while( ptotal<n )
+                {
+                    ptotal = ptotal*2;
+                }
+                while( ptotal<=m+n-1 )
+                {
+                    p = ptotal-n+1;
+                    flopcand = (int)Math.Ceiling((double)m/(double)p)*(2*ftbase.ftbasegetflopestimate(ptotal)+8*ptotal);
+                    if( (double)(flopcand)<(double)(flopbest) )
+                    {
+                        flopbest = flopcand;
+                        algbest = 2;
+                        q = p;
+                    }
+                    ptotal = ptotal*2;
+                }
+                alg = algbest;
+                convc1dx(a, m, b, n, circular, alg, q, ref r);
+                return;
+            }
+            
+            //
+            // straightforward formula for
+            // circular and non-circular convolutions.
+            //
+            // Very simple code, no further comments needed.
+            //
+            if( alg==0 )
+            {
+                
+                //
+                // Special case: N=1
+                //
+                if( n==1 )
+                {
+                    r = new complex[m];
+                    v = b[0];
+                    for(i_=0; i_<=m-1;i_++)
+                    {
+                        r[i_] = v*a[i_];
+                    }
+                    return;
+                }
+                
+                //
+                // use straightforward formula
+                //
+                if( circular )
+                {
+                    
+                    //
+                    // circular convolution
+                    //
+                    r = new complex[m];
+                    v = b[0];
+                    for(i_=0; i_<=m-1;i_++)
+                    {
+                        r[i_] = v*a[i_];
+                    }
+                    for(i=1; i<=n-1; i++)
+                    {
+                        v = b[i];
+                        i1 = 0;
+                        i2 = i-1;
+                        j1 = m-i;
+                        j2 = m-1;
+                        i1_ = (j1) - (i1);
+                        for(i_=i1; i_<=i2;i_++)
+                        {
+                            r[i_] = r[i_] + v*a[i_+i1_];
+                        }
+                        i1 = i;
+                        i2 = m-1;
+                        j1 = 0;
+                        j2 = m-i-1;
+                        i1_ = (j1) - (i1);
+                        for(i_=i1; i_<=i2;i_++)
+                        {
+                            r[i_] = r[i_] + v*a[i_+i1_];
+                        }
+                    }
+                }
+                else
+                {
+                    
+                    //
+                    // non-circular convolution
+                    //
+                    r = new complex[m+n-1];
+                    for(i=0; i<=m+n-2; i++)
+                    {
+                        r[i] = 0;
+                    }
+                    for(i=0; i<=n-1; i++)
+                    {
+                        v = b[i];
+                        i1_ = (0) - (i);
+                        for(i_=i; i_<=i+m-1;i_++)
+                        {
+                            r[i_] = r[i_] + v*a[i_+i1_];
+                        }
+                    }
+                }
+                return;
+            }
+            
+            //
+            // general FFT-based code for
+            // circular and non-circular convolutions.
+            //
+            // First, if convolution is circular, we test whether M is smooth or not.
+            // If it is smooth, we just use M-length FFT to calculate convolution.
+            // If it is not, we calculate non-circular convolution and wrap it arount.
+            //
+            // IF convolution is non-circular, we use zero-padding + FFT.
+            //
+            if( alg==1 )
+            {
+                if( circular && ftbase.ftbaseissmooth(m) )
+                {
+                    
+                    //
+                    // special code for circular convolution with smooth M
+                    //
+                    ftbase.ftbasegeneratecomplexfftplan(m, plan);
+                    buf = new double[2*m];
+                    for(i=0; i<=m-1; i++)
+                    {
+                        buf[2*i+0] = a[i].x;
+                        buf[2*i+1] = a[i].y;
+                    }
+                    buf2 = new double[2*m];
+                    for(i=0; i<=n-1; i++)
+                    {
+                        buf2[2*i+0] = b[i].x;
+                        buf2[2*i+1] = b[i].y;
+                    }
+                    for(i=n; i<=m-1; i++)
+                    {
+                        buf2[2*i+0] = 0;
+                        buf2[2*i+1] = 0;
+                    }
+                    ftbase.ftbaseexecuteplan(ref buf, 0, m, plan);
+                    ftbase.ftbaseexecuteplan(ref buf2, 0, m, plan);
+                    for(i=0; i<=m-1; i++)
+                    {
+                        ax = buf[2*i+0];
+                        ay = buf[2*i+1];
+                        bx = buf2[2*i+0];
+                        by = buf2[2*i+1];
+                        tx = ax*bx-ay*by;
+                        ty = ax*by+ay*bx;
+                        buf[2*i+0] = tx;
+                        buf[2*i+1] = -ty;
+                    }
+                    ftbase.ftbaseexecuteplan(ref buf, 0, m, plan);
+                    t = (double)1/(double)m;
+                    r = new complex[m];
+                    for(i=0; i<=m-1; i++)
+                    {
+                        r[i].x = t*buf[2*i+0];
+                        r[i].y = -(t*buf[2*i+1]);
+                    }
+                }
+                else
+                {
+                    
+                    //
+                    // M is non-smooth, general code (circular/non-circular):
+                    // * first part is the same for circular and non-circular
+                    //   convolutions. zero padding, FFTs, inverse FFTs
+                    // * second part differs:
+                    //   * for non-circular convolution we just copy array
+                    //   * for circular convolution we add array tail to its head
+                    //
+                    p = ftbase.ftbasefindsmooth(m+n-1);
+                    ftbase.ftbasegeneratecomplexfftplan(p, plan);
+                    buf = new double[2*p];
+                    for(i=0; i<=m-1; i++)
+                    {
+                        buf[2*i+0] = a[i].x;
+                        buf[2*i+1] = a[i].y;
+                    }
+                    for(i=m; i<=p-1; i++)
+                    {
+                        buf[2*i+0] = 0;
+                        buf[2*i+1] = 0;
+                    }
+                    buf2 = new double[2*p];
+                    for(i=0; i<=n-1; i++)
+                    {
+                        buf2[2*i+0] = b[i].x;
+                        buf2[2*i+1] = b[i].y;
+                    }
+                    for(i=n; i<=p-1; i++)
+                    {
+                        buf2[2*i+0] = 0;
+                        buf2[2*i+1] = 0;
+                    }
+                    ftbase.ftbaseexecuteplan(ref buf, 0, p, plan);
+                    ftbase.ftbaseexecuteplan(ref buf2, 0, p, plan);
+                    for(i=0; i<=p-1; i++)
+                    {
+                        ax = buf[2*i+0];
+                        ay = buf[2*i+1];
+                        bx = buf2[2*i+0];
+                        by = buf2[2*i+1];
+                        tx = ax*bx-ay*by;
+                        ty = ax*by+ay*bx;
+                        buf[2*i+0] = tx;
+                        buf[2*i+1] = -ty;
+                    }
+                    ftbase.ftbaseexecuteplan(ref buf, 0, p, plan);
+                    t = (double)1/(double)p;
+                    if( circular )
+                    {
+                        
+                        //
+                        // circular, add tail to head
+                        //
+                        r = new complex[m];
+                        for(i=0; i<=m-1; i++)
+                        {
+                            r[i].x = t*buf[2*i+0];
+                            r[i].y = -(t*buf[2*i+1]);
+                        }
+                        for(i=m; i<=m+n-2; i++)
+                        {
+                            r[i-m].x = r[i-m].x+t*buf[2*i+0];
+                            r[i-m].y = r[i-m].y-t*buf[2*i+1];
+                        }
+                    }
+                    else
+                    {
+                        
+                        //
+                        // non-circular, just copy
+                        //
+                        r = new complex[m+n-1];
+                        for(i=0; i<=m+n-2; i++)
+                        {
+                            r[i].x = t*buf[2*i+0];
+                            r[i].y = -(t*buf[2*i+1]);
+                        }
+                    }
+                }
+                return;
+            }
+            
+            //
+            // overlap-add method for
+            // circular and non-circular convolutions.
+            //
+            // First part of code (separate FFTs of input blocks) is the same
+            // for all types of convolution. Second part (overlapping outputs)
+            // differs for different types of convolution. We just copy output
+            // when convolution is non-circular. We wrap it around, if it is
+            // circular.
+            //
+            if( alg==2 )
+            {
+                buf = new double[2*(q+n-1)];
+                
+                //
+                // prepare R
+                //
+                if( circular )
+                {
+                    r = new complex[m];
+                    for(i=0; i<=m-1; i++)
+                    {
+                        r[i] = 0;
+                    }
+                }
+                else
+                {
+                    r = new complex[m+n-1];
+                    for(i=0; i<=m+n-2; i++)
+                    {
+                        r[i] = 0;
+                    }
+                }
+                
+                //
+                // pre-calculated FFT(B)
+                //
+                bbuf = new complex[q+n-1];
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    bbuf[i_] = b[i_];
+                }
+                for(j=n; j<=q+n-2; j++)
+                {
+                    bbuf[j] = 0;
+                }
+                fft.fftc1d(ref bbuf, q+n-1);
+                
+                //
+                // prepare FFT plan for chunks of A
+                //
+                ftbase.ftbasegeneratecomplexfftplan(q+n-1, plan);
+                
+                //
+                // main overlap-add cycle
+                //
+                i = 0;
+                while( i<=m-1 )
+                {
+                    p = Math.Min(q, m-i);
+                    for(j=0; j<=p-1; j++)
+                    {
+                        buf[2*j+0] = a[i+j].x;
+                        buf[2*j+1] = a[i+j].y;
+                    }
+                    for(j=p; j<=q+n-2; j++)
+                    {
+                        buf[2*j+0] = 0;
+                        buf[2*j+1] = 0;
+                    }
+                    ftbase.ftbaseexecuteplan(ref buf, 0, q+n-1, plan);
+                    for(j=0; j<=q+n-2; j++)
+                    {
+                        ax = buf[2*j+0];
+                        ay = buf[2*j+1];
+                        bx = bbuf[j].x;
+                        by = bbuf[j].y;
+                        tx = ax*bx-ay*by;
+                        ty = ax*by+ay*bx;
+                        buf[2*j+0] = tx;
+                        buf[2*j+1] = -ty;
+                    }
+                    ftbase.ftbaseexecuteplan(ref buf, 0, q+n-1, plan);
+                    t = (double)1/(double)(q+n-1);
+                    if( circular )
+                    {
+                        j1 = Math.Min(i+p+n-2, m-1)-i;
+                        j2 = j1+1;
+                    }
+                    else
+                    {
+                        j1 = p+n-2;
+                        j2 = j1+1;
+                    }
+                    for(j=0; j<=j1; j++)
+                    {
+                        r[i+j].x = r[i+j].x+buf[2*j+0]*t;
+                        r[i+j].y = r[i+j].y-buf[2*j+1]*t;
+                    }
+                    for(j=j2; j<=p+n-2; j++)
+                    {
+                        r[j-j2].x = r[j-j2].x+buf[2*j+0]*t;
+                        r[j-j2].y = r[j-j2].y-buf[2*j+1]*t;
+                    }
+                    i = i+p;
+                }
+                return;
+            }
+        }
+
+
+        /*************************************************************************
+        1-dimensional real convolution.
+
+        Extended subroutine which allows to choose convolution algorithm.
+        Intended for internal use, ALGLIB users should call ConvR1D().
+
+        INPUT PARAMETERS
+            A   -   array[0..M-1] - complex function to be transformed
+            M   -   problem size
+            B   -   array[0..N-1] - complex function to be transformed
+            N   -   problem size, N<=M
+            Alg -   algorithm type:
+                    *-2     auto-select Q for overlap-add
+                    *-1     auto-select algorithm and parameters
+                    * 0     straightforward formula for small N's
+                    * 1     general FFT-based code
+                    * 2     overlap-add with length Q
+            Q   -   length for overlap-add
+
+        OUTPUT PARAMETERS
+            R   -   convolution: A*B. array[0..N+M-1].
+
+          -- ALGLIB --
+             Copyright 21.07.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void convr1dx(double[] a,
+            int m,
+            double[] b,
+            int n,
+            bool circular,
+            int alg,
+            int q,
+            ref double[] r)
+        {
+            double v = 0;
+            int i = 0;
+            int j = 0;
+            int p = 0;
+            int ptotal = 0;
+            int i1 = 0;
+            int i2 = 0;
+            int j1 = 0;
+            int j2 = 0;
+            double ax = 0;
+            double ay = 0;
+            double bx = 0;
+            double by = 0;
+            double tx = 0;
+            double ty = 0;
+            double flopcand = 0;
+            double flopbest = 0;
+            int algbest = 0;
+            ftbase.ftplan plan = new ftbase.ftplan();
+            double[] buf = new double[0];
+            double[] buf2 = new double[0];
+            double[] buf3 = new double[0];
+            int i_ = 0;
+            int i1_ = 0;
+
+            r = new double[0];
+
+            alglib.ap.assert(n>0 && m>0, "ConvC1DX: incorrect N or M!");
+            alglib.ap.assert(n<=m, "ConvC1DX: N<M assumption is false!");
+            
+            //
+            // handle special cases
+            //
+            if( Math.Min(m, n)<=2 )
+            {
+                alg = 0;
+            }
+            
+            //
+            // Auto-select
+            //
+            if( alg<0 )
+            {
+                
+                //
+                // Initial candidate: straightforward implementation.
+                //
+                // If we want to use auto-fitted overlap-add,
+                // flop count is initialized by large real number - to force
+                // another algorithm selection
+                //
+                algbest = 0;
+                if( alg==-1 )
+                {
+                    flopbest = 0.15*m*n;
+                }
+                else
+                {
+                    flopbest = math.maxrealnumber;
+                }
+                
+                //
+                // Another candidate - generic FFT code
+                //
+                if( alg==-1 )
+                {
+                    if( (circular && ftbase.ftbaseissmooth(m)) && m%2==0 )
+                    {
+                        
+                        //
+                        // special code for circular convolution of a sequence with a smooth length
+                        //
+                        flopcand = 3*ftbase.ftbasegetflopestimate(m/2)+(double)(6*m)/(double)2;
+                        if( (double)(flopcand)<(double)(flopbest) )
+                        {
+                            algbest = 1;
+                            flopbest = flopcand;
+                        }
+                    }
+                    else
+                    {
+                        
+                        //
+                        // general cyclic/non-cyclic convolution
+                        //
+                        p = ftbase.ftbasefindsmootheven(m+n-1);
+                        flopcand = 3*ftbase.ftbasegetflopestimate(p/2)+(double)(6*p)/(double)2;
+                        if( (double)(flopcand)<(double)(flopbest) )
+                        {
+                            algbest = 1;
+                            flopbest = flopcand;
+                        }
+                    }
+                }
+                
+                //
+                // Another candidate - overlap-add
+                //
+                q = 1;
+                ptotal = 1;
+                while( ptotal<n )
+                {
+                    ptotal = ptotal*2;
+                }
+                while( ptotal<=m+n-1 )
+                {
+                    p = ptotal-n+1;
+                    flopcand = (int)Math.Ceiling((double)m/(double)p)*(2*ftbase.ftbasegetflopestimate(ptotal/2)+1*(ptotal/2));
+                    if( (double)(flopcand)<(double)(flopbest) )
+                    {
+                        flopbest = flopcand;
+                        algbest = 2;
+                        q = p;
+                    }
+                    ptotal = ptotal*2;
+                }
+                alg = algbest;
+                convr1dx(a, m, b, n, circular, alg, q, ref r);
+                return;
+            }
+            
+            //
+            // straightforward formula for
+            // circular and non-circular convolutions.
+            //
+            // Very simple code, no further comments needed.
+            //
+            if( alg==0 )
+            {
+                
+                //
+                // Special case: N=1
+                //
+                if( n==1 )
+                {
+                    r = new double[m];
+                    v = b[0];
+                    for(i_=0; i_<=m-1;i_++)
+                    {
+                        r[i_] = v*a[i_];
+                    }
+                    return;
+                }
+                
+                //
+                // use straightforward formula
+                //
+                if( circular )
+                {
+                    
+                    //
+                    // circular convolution
+                    //
+                    r = new double[m];
+                    v = b[0];
+                    for(i_=0; i_<=m-1;i_++)
+                    {
+                        r[i_] = v*a[i_];
+                    }
+                    for(i=1; i<=n-1; i++)
+                    {
+                        v = b[i];
+                        i1 = 0;
+                        i2 = i-1;
+                        j1 = m-i;
+                        j2 = m-1;
+                        i1_ = (j1) - (i1);
+                        for(i_=i1; i_<=i2;i_++)
+                        {
+                            r[i_] = r[i_] + v*a[i_+i1_];
+                        }
+                        i1 = i;
+                        i2 = m-1;
+                        j1 = 0;
+                        j2 = m-i-1;
+                        i1_ = (j1) - (i1);
+                        for(i_=i1; i_<=i2;i_++)
+                        {
+                            r[i_] = r[i_] + v*a[i_+i1_];
+                        }
+                    }
+                }
+                else
+                {
+                    
+                    //
+                    // non-circular convolution
+                    //
+                    r = new double[m+n-1];
+                    for(i=0; i<=m+n-2; i++)
+                    {
+                        r[i] = 0;
+                    }
+                    for(i=0; i<=n-1; i++)
+                    {
+                        v = b[i];
+                        i1_ = (0) - (i);
+                        for(i_=i; i_<=i+m-1;i_++)
+                        {
+                            r[i_] = r[i_] + v*a[i_+i1_];
+                        }
+                    }
+                }
+                return;
+            }
+            
+            //
+            // general FFT-based code for
+            // circular and non-circular convolutions.
+            //
+            // First, if convolution is circular, we test whether M is smooth or not.
+            // If it is smooth, we just use M-length FFT to calculate convolution.
+            // If it is not, we calculate non-circular convolution and wrap it arount.
+            //
+            // If convolution is non-circular, we use zero-padding + FFT.
+            //
+            // We assume that M+N-1>2 - we should call small case code otherwise
+            //
+            if( alg==1 )
+            {
+                alglib.ap.assert(m+n-1>2, "ConvR1DX: internal error!");
+                if( (circular && ftbase.ftbaseissmooth(m)) && m%2==0 )
+                {
+                    
+                    //
+                    // special code for circular convolution with smooth even M
+                    //
+                    buf = new double[m];
+                    for(i_=0; i_<=m-1;i_++)
+                    {
+                        buf[i_] = a[i_];
+                    }
+                    buf2 = new double[m];
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        buf2[i_] = b[i_];
+                    }
+                    for(i=n; i<=m-1; i++)
+                    {
+                        buf2[i] = 0;
+                    }
+                    buf3 = new double[m];
+                    ftbase.ftbasegeneratecomplexfftplan(m/2, plan);
+                    fft.fftr1dinternaleven(ref buf, m, ref buf3, plan);
+                    fft.fftr1dinternaleven(ref buf2, m, ref buf3, plan);
+                    buf[0] = buf[0]*buf2[0];
+                    buf[1] = buf[1]*buf2[1];
+                    for(i=1; i<=m/2-1; i++)
+                    {
+                        ax = buf[2*i+0];
+                        ay = buf[2*i+1];
+                        bx = buf2[2*i+0];
+                        by = buf2[2*i+1];
+                        tx = ax*bx-ay*by;
+                        ty = ax*by+ay*bx;
+                        buf[2*i+0] = tx;
+                        buf[2*i+1] = ty;
+                    }
+                    fft.fftr1dinvinternaleven(ref buf, m, ref buf3, plan);
+                    r = new double[m];
+                    for(i_=0; i_<=m-1;i_++)
+                    {
+                        r[i_] = buf[i_];
+                    }
+                }
+                else
+                {
+                    
+                    //
+                    // M is non-smooth or non-even, general code (circular/non-circular):
+                    // * first part is the same for circular and non-circular
+                    //   convolutions. zero padding, FFTs, inverse FFTs
+                    // * second part differs:
+                    //   * for non-circular convolution we just copy array
+                    //   * for circular convolution we add array tail to its head
+                    //
+                    p = ftbase.ftbasefindsmootheven(m+n-1);
+                    buf = new double[p];
+                    for(i_=0; i_<=m-1;i_++)
+                    {
+                        buf[i_] = a[i_];
+                    }
+                    for(i=m; i<=p-1; i++)
+                    {
+                        buf[i] = 0;
+                    }
+                    buf2 = new double[p];
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        buf2[i_] = b[i_];
+                    }
+                    for(i=n; i<=p-1; i++)
+                    {
+                        buf2[i] = 0;
+                    }
+                    buf3 = new double[p];
+                    ftbase.ftbasegeneratecomplexfftplan(p/2, plan);
+                    fft.fftr1dinternaleven(ref buf, p, ref buf3, plan);
+                    fft.fftr1dinternaleven(ref buf2, p, ref buf3, plan);
+                    buf[0] = buf[0]*buf2[0];
+                    buf[1] = buf[1]*buf2[1];
+                    for(i=1; i<=p/2-1; i++)
+                    {
+                        ax = buf[2*i+0];
+                        ay = buf[2*i+1];
+                        bx = buf2[2*i+0];
+                        by = buf2[2*i+1];
+                        tx = ax*bx-ay*by;
+                        ty = ax*by+ay*bx;
+                        buf[2*i+0] = tx;
+                        buf[2*i+1] = ty;
+                    }
+                    fft.fftr1dinvinternaleven(ref buf, p, ref buf3, plan);
+                    if( circular )
+                    {
+                        
+                        //
+                        // circular, add tail to head
+                        //
+                        r = new double[m];
+                        for(i_=0; i_<=m-1;i_++)
+                        {
+                            r[i_] = buf[i_];
+                        }
+                        if( n>=2 )
+                        {
+                            i1_ = (m) - (0);
+                            for(i_=0; i_<=n-2;i_++)
+                            {
+                                r[i_] = r[i_] + buf[i_+i1_];
+                            }
+                        }
+                    }
+                    else
+                    {
+                        
+                        //
+                        // non-circular, just copy
+                        //
+                        r = new double[m+n-1];
+                        for(i_=0; i_<=m+n-2;i_++)
+                        {
+                            r[i_] = buf[i_];
+                        }
+                    }
+                }
+                return;
+            }
+            
+            //
+            // overlap-add method
+            //
+            if( alg==2 )
+            {
+                alglib.ap.assert((q+n-1)%2==0, "ConvR1DX: internal error!");
+                buf = new double[q+n-1];
+                buf2 = new double[q+n-1];
+                buf3 = new double[q+n-1];
+                ftbase.ftbasegeneratecomplexfftplan((q+n-1)/2, plan);
+                
+                //
+                // prepare R
+                //
+                if( circular )
+                {
+                    r = new double[m];
+                    for(i=0; i<=m-1; i++)
+                    {
+                        r[i] = 0;
+                    }
+                }
+                else
+                {
+                    r = new double[m+n-1];
+                    for(i=0; i<=m+n-2; i++)
+                    {
+                        r[i] = 0;
+                    }
+                }
+                
+                //
+                // pre-calculated FFT(B)
+                //
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    buf2[i_] = b[i_];
+                }
+                for(j=n; j<=q+n-2; j++)
+                {
+                    buf2[j] = 0;
+                }
+                fft.fftr1dinternaleven(ref buf2, q+n-1, ref buf3, plan);
+                
+                //
+                // main overlap-add cycle
+                //
+                i = 0;
+                while( i<=m-1 )
+                {
+                    p = Math.Min(q, m-i);
+                    i1_ = (i) - (0);
+                    for(i_=0; i_<=p-1;i_++)
+                    {
+                        buf[i_] = a[i_+i1_];
+                    }
+                    for(j=p; j<=q+n-2; j++)
+                    {
+                        buf[j] = 0;
+                    }
+                    fft.fftr1dinternaleven(ref buf, q+n-1, ref buf3, plan);
+                    buf[0] = buf[0]*buf2[0];
+                    buf[1] = buf[1]*buf2[1];
+                    for(j=1; j<=(q+n-1)/2-1; j++)
+                    {
+                        ax = buf[2*j+0];
+                        ay = buf[2*j+1];
+                        bx = buf2[2*j+0];
+                        by = buf2[2*j+1];
+                        tx = ax*bx-ay*by;
+                        ty = ax*by+ay*bx;
+                        buf[2*j+0] = tx;
+                        buf[2*j+1] = ty;
+                    }
+                    fft.fftr1dinvinternaleven(ref buf, q+n-1, ref buf3, plan);
+                    if( circular )
+                    {
+                        j1 = Math.Min(i+p+n-2, m-1)-i;
+                        j2 = j1+1;
+                    }
+                    else
+                    {
+                        j1 = p+n-2;
+                        j2 = j1+1;
+                    }
+                    i1_ = (0) - (i);
+                    for(i_=i; i_<=i+j1;i_++)
+                    {
+                        r[i_] = r[i_] + buf[i_+i1_];
+                    }
+                    if( p+n-2>=j2 )
+                    {
+                        i1_ = (j2) - (0);
+                        for(i_=0; i_<=p+n-2-j2;i_++)
+                        {
+                            r[i_] = r[i_] + buf[i_+i1_];
+                        }
+                    }
+                    i = i+p;
+                }
+                return;
+            }
+        }
+
+
+    }
+    public class corr
+    {
+        /*************************************************************************
+        1-dimensional complex cross-correlation.
+
+        For given Pattern/Signal returns corr(Pattern,Signal) (non-circular).
+
+        Correlation is calculated using reduction to  convolution.  Algorithm with
+        max(N,N)*log(max(N,N)) complexity is used (see  ConvC1D()  for  more  info
+        about performance).
+
+        IMPORTANT:
+            for  historical reasons subroutine accepts its parameters in  reversed
+            order: CorrC1D(Signal, Pattern) = Pattern x Signal (using  traditional
+            definition of cross-correlation, denoting cross-correlation as "x").
+
+        INPUT PARAMETERS
+            Signal  -   array[0..N-1] - complex function to be transformed,
+                        signal containing pattern
+            N       -   problem size
+            Pattern -   array[0..M-1] - complex function to be transformed,
+                        pattern to search withing signal
+            M       -   problem size
+
+        OUTPUT PARAMETERS
+            R       -   cross-correlation, array[0..N+M-2]:
+                        * positive lags are stored in R[0..N-1],
+                          R[i] = sum(conj(pattern[j])*signal[i+j]
+                        * negative lags are stored in R[N..N+M-2],
+                          R[N+M-1-i] = sum(conj(pattern[j])*signal[-i+j]
+
+        NOTE:
+            It is assumed that pattern domain is [0..M-1].  If Pattern is non-zero
+        on [-K..M-1],  you can still use this subroutine, just shift result by K.
+
+          -- ALGLIB --
+             Copyright 21.07.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void corrc1d(complex[] signal,
+            int n,
+            complex[] pattern,
+            int m,
+            ref complex[] r)
+        {
+            complex[] p = new complex[0];
+            complex[] b = new complex[0];
+            int i = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            r = new complex[0];
+
+            alglib.ap.assert(n>0 && m>0, "CorrC1D: incorrect N or M!");
+            p = new complex[m];
+            for(i=0; i<=m-1; i++)
+            {
+                p[m-1-i] = math.conj(pattern[i]);
+            }
+            conv.convc1d(p, m, signal, n, ref b);
+            r = new complex[m+n-1];
+            i1_ = (m-1) - (0);
+            for(i_=0; i_<=n-1;i_++)
+            {
+                r[i_] = b[i_+i1_];
+            }
+            if( m+n-2>=n )
+            {
+                i1_ = (0) - (n);
+                for(i_=n; i_<=m+n-2;i_++)
+                {
+                    r[i_] = b[i_+i1_];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        1-dimensional circular complex cross-correlation.
+
+        For given Pattern/Signal returns corr(Pattern,Signal) (circular).
+        Algorithm has linearithmic complexity for any M/N.
+
+        IMPORTANT:
+            for  historical reasons subroutine accepts its parameters in  reversed
+            order:   CorrC1DCircular(Signal, Pattern) = Pattern x Signal    (using
+            traditional definition of cross-correlation, denoting cross-correlation
+            as "x").
+
+        INPUT PARAMETERS
+            Signal  -   array[0..N-1] - complex function to be transformed,
+                        periodic signal containing pattern
+            N       -   problem size
+            Pattern -   array[0..M-1] - complex function to be transformed,
+                        non-periodic pattern to search withing signal
+            M       -   problem size
+
+        OUTPUT PARAMETERS
+            R   -   convolution: A*B. array[0..M-1].
+
+
+          -- ALGLIB --
+             Copyright 21.07.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void corrc1dcircular(complex[] signal,
+            int m,
+            complex[] pattern,
+            int n,
+            ref complex[] c)
+        {
+            complex[] p = new complex[0];
+            complex[] b = new complex[0];
+            int i1 = 0;
+            int i2 = 0;
+            int i = 0;
+            int j2 = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            c = new complex[0];
+
+            alglib.ap.assert(n>0 && m>0, "ConvC1DCircular: incorrect N or M!");
+            
+            //
+            // normalize task: make M>=N,
+            // so A will be longer (at least - not shorter) that B.
+            //
+            if( m<n )
+            {
+                b = new complex[m];
+                for(i1=0; i1<=m-1; i1++)
+                {
+                    b[i1] = 0;
+                }
+                i1 = 0;
+                while( i1<n )
+                {
+                    i2 = Math.Min(i1+m-1, n-1);
+                    j2 = i2-i1;
+                    i1_ = (i1) - (0);
+                    for(i_=0; i_<=j2;i_++)
+                    {
+                        b[i_] = b[i_] + pattern[i_+i1_];
+                    }
+                    i1 = i1+m;
+                }
+                corrc1dcircular(signal, m, b, m, ref c);
+                return;
+            }
+            
+            //
+            // Task is normalized
+            //
+            p = new complex[n];
+            for(i=0; i<=n-1; i++)
+            {
+                p[n-1-i] = math.conj(pattern[i]);
+            }
+            conv.convc1dcircular(signal, m, p, n, ref b);
+            c = new complex[m];
+            i1_ = (n-1) - (0);
+            for(i_=0; i_<=m-n;i_++)
+            {
+                c[i_] = b[i_+i1_];
+            }
+            if( m-n+1<=m-1 )
+            {
+                i1_ = (0) - (m-n+1);
+                for(i_=m-n+1; i_<=m-1;i_++)
+                {
+                    c[i_] = b[i_+i1_];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        1-dimensional real cross-correlation.
+
+        For given Pattern/Signal returns corr(Pattern,Signal) (non-circular).
+
+        Correlation is calculated using reduction to  convolution.  Algorithm with
+        max(N,N)*log(max(N,N)) complexity is used (see  ConvC1D()  for  more  info
+        about performance).
+
+        IMPORTANT:
+            for  historical reasons subroutine accepts its parameters in  reversed
+            order: CorrR1D(Signal, Pattern) = Pattern x Signal (using  traditional
+            definition of cross-correlation, denoting cross-correlation as "x").
+
+        INPUT PARAMETERS
+            Signal  -   array[0..N-1] - real function to be transformed,
+                        signal containing pattern
+            N       -   problem size
+            Pattern -   array[0..M-1] - real function to be transformed,
+                        pattern to search withing signal
+            M       -   problem size
+
+        OUTPUT PARAMETERS
+            R       -   cross-correlation, array[0..N+M-2]:
+                        * positive lags are stored in R[0..N-1],
+                          R[i] = sum(pattern[j]*signal[i+j]
+                        * negative lags are stored in R[N..N+M-2],
+                          R[N+M-1-i] = sum(pattern[j]*signal[-i+j]
+
+        NOTE:
+            It is assumed that pattern domain is [0..M-1].  If Pattern is non-zero
+        on [-K..M-1],  you can still use this subroutine, just shift result by K.
+
+          -- ALGLIB --
+             Copyright 21.07.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void corrr1d(double[] signal,
+            int n,
+            double[] pattern,
+            int m,
+            ref double[] r)
+        {
+            double[] p = new double[0];
+            double[] b = new double[0];
+            int i = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            r = new double[0];
+
+            alglib.ap.assert(n>0 && m>0, "CorrR1D: incorrect N or M!");
+            p = new double[m];
+            for(i=0; i<=m-1; i++)
+            {
+                p[m-1-i] = pattern[i];
+            }
+            conv.convr1d(p, m, signal, n, ref b);
+            r = new double[m+n-1];
+            i1_ = (m-1) - (0);
+            for(i_=0; i_<=n-1;i_++)
+            {
+                r[i_] = b[i_+i1_];
+            }
+            if( m+n-2>=n )
+            {
+                i1_ = (0) - (n);
+                for(i_=n; i_<=m+n-2;i_++)
+                {
+                    r[i_] = b[i_+i1_];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        1-dimensional circular real cross-correlation.
+
+        For given Pattern/Signal returns corr(Pattern,Signal) (circular).
+        Algorithm has linearithmic complexity for any M/N.
+
+        IMPORTANT:
+            for  historical reasons subroutine accepts its parameters in  reversed
+            order:   CorrR1DCircular(Signal, Pattern) = Pattern x Signal    (using
+            traditional definition of cross-correlation, denoting cross-correlation
+            as "x").
+
+        INPUT PARAMETERS
+            Signal  -   array[0..N-1] - real function to be transformed,
+                        periodic signal containing pattern
+            N       -   problem size
+            Pattern -   array[0..M-1] - real function to be transformed,
+                        non-periodic pattern to search withing signal
+            M       -   problem size
+
+        OUTPUT PARAMETERS
+            R   -   convolution: A*B. array[0..M-1].
+
+
+          -- ALGLIB --
+             Copyright 21.07.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void corrr1dcircular(double[] signal,
+            int m,
+            double[] pattern,
+            int n,
+            ref double[] c)
+        {
+            double[] p = new double[0];
+            double[] b = new double[0];
+            int i1 = 0;
+            int i2 = 0;
+            int i = 0;
+            int j2 = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            c = new double[0];
+
+            alglib.ap.assert(n>0 && m>0, "ConvC1DCircular: incorrect N or M!");
+            
+            //
+            // normalize task: make M>=N,
+            // so A will be longer (at least - not shorter) that B.
+            //
+            if( m<n )
+            {
+                b = new double[m];
+                for(i1=0; i1<=m-1; i1++)
+                {
+                    b[i1] = 0;
+                }
+                i1 = 0;
+                while( i1<n )
+                {
+                    i2 = Math.Min(i1+m-1, n-1);
+                    j2 = i2-i1;
+                    i1_ = (i1) - (0);
+                    for(i_=0; i_<=j2;i_++)
+                    {
+                        b[i_] = b[i_] + pattern[i_+i1_];
+                    }
+                    i1 = i1+m;
+                }
+                corrr1dcircular(signal, m, b, m, ref c);
+                return;
+            }
+            
+            //
+            // Task is normalized
+            //
+            p = new double[n];
+            for(i=0; i<=n-1; i++)
+            {
+                p[n-1-i] = pattern[i];
+            }
+            conv.convr1dcircular(signal, m, p, n, ref b);
+            c = new double[m];
+            i1_ = (n-1) - (0);
+            for(i_=0; i_<=m-n;i_++)
+            {
+                c[i_] = b[i_+i1_];
+            }
+            if( m-n+1<=m-1 )
+            {
+                i1_ = (0) - (m-n+1);
+                for(i_=m-n+1; i_<=m-1;i_++)
+                {
+                    c[i_] = b[i_+i1_];
+                }
+            }
+        }
+
+
+    }
+    public class fht
+    {
+        /*************************************************************************
+        1-dimensional Fast Hartley Transform.
+
+        Algorithm has O(N*logN) complexity for any N (composite or prime).
+
+        INPUT PARAMETERS
+            A   -   array[0..N-1] - real function to be transformed
+            N   -   problem size
+            
+        OUTPUT PARAMETERS
+            A   -   FHT of a input array, array[0..N-1],
+                    A_out[k] = sum(A_in[j]*(cos(2*pi*j*k/N)+sin(2*pi*j*k/N)), j=0..N-1)
+
+
+          -- ALGLIB --
+             Copyright 04.06.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void fhtr1d(ref double[] a,
+            int n)
+        {
+            ftbase.ftplan plan = new ftbase.ftplan();
+            int i = 0;
+            complex[] fa = new complex[0];
+
+            alglib.ap.assert(n>0, "FHTR1D: incorrect N!");
+            
+            //
+            // Special case: N=1, FHT is just identity transform.
+            // After this block we assume that N is strictly greater than 1.
+            //
+            if( n==1 )
+            {
+                return;
+            }
+            
+            //
+            // Reduce FHt to real FFT
+            //
+            fft.fftr1d(a, n, ref fa);
+            for(i=0; i<=n-1; i++)
+            {
+                a[i] = fa[i].x-fa[i].y;
+            }
+        }
+
+
+        /*************************************************************************
+        1-dimensional inverse FHT.
+
+        Algorithm has O(N*logN) complexity for any N (composite or prime).
+
+        INPUT PARAMETERS
+            A   -   array[0..N-1] - complex array to be transformed
+            N   -   problem size
+
+        OUTPUT PARAMETERS
+            A   -   inverse FHT of a input array, array[0..N-1]
+
+
+          -- ALGLIB --
+             Copyright 29.05.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void fhtr1dinv(ref double[] a,
+            int n)
+        {
+            int i = 0;
+
+            alglib.ap.assert(n>0, "FHTR1DInv: incorrect N!");
+            
+            //
+            // Special case: N=1, iFHT is just identity transform.
+            // After this block we assume that N is strictly greater than 1.
+            //
+            if( n==1 )
+            {
+                return;
+            }
+            
+            //
+            // Inverse FHT can be expressed in terms of the FHT as
+            //
+            //     invfht(x) = fht(x)/N
+            //
+            fhtr1d(ref a, n);
+            for(i=0; i<=n-1; i++)
+            {
+                a[i] = a[i]/n;
+            }
+        }
+
+
+    }
+}
+
Index: /trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.5.0/ALGLIB-3.5.0/integration.cs
===================================================================
--- /trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.5.0/ALGLIB-3.5.0/integration.cs	(revision 7673)
+++ /trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.5.0/ALGLIB-3.5.0/integration.cs	(revision 7673)
@@ -0,0 +1,3551 @@
+/*************************************************************************
+Copyright (c) Sergey Bochkanov (ALGLIB project).
+
+>>> SOURCE LICENSE >>>
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation (www.fsf.org); either version 2 of the
+License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+A copy of the GNU General Public License is available at
+http://www.fsf.org/licensing/licenses
+>>> END OF LICENSE >>>
+*************************************************************************/
+#pragma warning disable 162
+#pragma warning disable 219
+using System;
+
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    Computation of nodes and weights for a Gauss quadrature formula
+
+    The algorithm generates the N-point Gauss quadrature formula  with  weight
+    function given by coefficients alpha and beta  of  a  recurrence  relation
+    which generates a system of orthogonal polynomials:
+
+    P-1(x)   =  0
+    P0(x)    =  1
+    Pn+1(x)  =  (x-alpha(n))*Pn(x)  -  beta(n)*Pn-1(x)
+
+    and zeroth moment Mu0
+
+    Mu0 = integral(W(x)dx,a,b)
+
+    INPUT PARAMETERS:
+        Alpha      array[0..N-1], alpha coefficients
+        Beta       array[0..N-1], beta coefficients
+                    Zero-indexed element is not used and may be arbitrary.
+                    Beta[I]>0.
+        Mu0        zeroth moment of the weight function.
+        N          number of nodes of the quadrature formula, N>=1
+
+    OUTPUT PARAMETERS:
+        Info    -   error code:
+                    * -3    internal eigenproblem solver hasn't converged
+                    * -2    Beta[i]<=0
+                    * -1    incorrect N was passed
+                    *  1    OK
+        X       -   array[0..N-1] - array of quadrature nodes,
+                    in ascending order.
+        W       -   array[0..N-1] - array of quadrature weights.
+
+      -- ALGLIB --
+         Copyright 2005-2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void gqgeneraterec(double[] alpha, double[] beta, double mu0, int n, out int info, out double[] x, out double[] w)
+    {
+        info = 0;
+        x = new double[0];
+        w = new double[0];
+        gq.gqgeneraterec(alpha, beta, mu0, n, ref info, ref x, ref w);
+        return;
+    }
+
+    /*************************************************************************
+    Computation of nodes and weights for a Gauss-Lobatto quadrature formula
+
+    The algorithm generates the N-point Gauss-Lobatto quadrature formula  with
+    weight function given by coefficients alpha and beta of a recurrence which
+    generates a system of orthogonal polynomials.
+
+    P-1(x)   =  0
+    P0(x)    =  1
+    Pn+1(x)  =  (x-alpha(n))*Pn(x)  -  beta(n)*Pn-1(x)
+
+    and zeroth moment Mu0
+
+    Mu0 = integral(W(x)dx,a,b)
+
+    INPUT PARAMETERS:
+        Alpha      array[0..N-2], alpha coefficients
+        Beta       array[0..N-2], beta coefficients.
+                    Zero-indexed element is not used, may be arbitrary.
+                    Beta[I]>0
+        Mu0        zeroth moment of the weighting function.
+        A          left boundary of the integration interval.
+        B          right boundary of the integration interval.
+        N          number of nodes of the quadrature formula, N>=3
+                    (including the left and right boundary nodes).
+
+    OUTPUT PARAMETERS:
+        Info    -   error code:
+                    * -3    internal eigenproblem solver hasn't converged
+                    * -2    Beta[i]<=0
+                    * -1    incorrect N was passed
+                    *  1    OK
+        X       -   array[0..N-1] - array of quadrature nodes,
+                    in ascending order.
+        W       -   array[0..N-1] - array of quadrature weights.
+
+      -- ALGLIB --
+         Copyright 2005-2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void gqgenerategausslobattorec(double[] alpha, double[] beta, double mu0, double a, double b, int n, out int info, out double[] x, out double[] w)
+    {
+        info = 0;
+        x = new double[0];
+        w = new double[0];
+        gq.gqgenerategausslobattorec(alpha, beta, mu0, a, b, n, ref info, ref x, ref w);
+        return;
+    }
+
+    /*************************************************************************
+    Computation of nodes and weights for a Gauss-Radau quadrature formula
+
+    The algorithm generates the N-point Gauss-Radau  quadrature  formula  with
+    weight function given by the coefficients alpha and  beta  of a recurrence
+    which generates a system of orthogonal polynomials.
+
+    P-1(x)   =  0
+    P0(x)    =  1
+    Pn+1(x)  =  (x-alpha(n))*Pn(x)  -  beta(n)*Pn-1(x)
+
+    and zeroth moment Mu0
+
+    Mu0 = integral(W(x)dx,a,b)
+
+    INPUT PARAMETERS:
+        Alpha      array[0..N-2], alpha coefficients.
+        Beta       array[0..N-1], beta coefficients
+                    Zero-indexed element is not used.
+                    Beta[I]>0
+        Mu0        zeroth moment of the weighting function.
+        A          left boundary of the integration interval.
+        N          number of nodes of the quadrature formula, N>=2
+                    (including the left boundary node).
+
+    OUTPUT PARAMETERS:
+        Info    -   error code:
+                    * -3    internal eigenproblem solver hasn't converged
+                    * -2    Beta[i]<=0
+                    * -1    incorrect N was passed
+                    *  1    OK
+        X       -   array[0..N-1] - array of quadrature nodes,
+                    in ascending order.
+        W       -   array[0..N-1] - array of quadrature weights.
+
+
+      -- ALGLIB --
+         Copyright 2005-2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void gqgenerategaussradaurec(double[] alpha, double[] beta, double mu0, double a, int n, out int info, out double[] x, out double[] w)
+    {
+        info = 0;
+        x = new double[0];
+        w = new double[0];
+        gq.gqgenerategaussradaurec(alpha, beta, mu0, a, n, ref info, ref x, ref w);
+        return;
+    }
+
+    /*************************************************************************
+    Returns nodes/weights for Gauss-Legendre quadrature on [-1,1] with N
+    nodes.
+
+    INPUT PARAMETERS:
+        N           -   number of nodes, >=1
+
+    OUTPUT PARAMETERS:
+        Info        -   error code:
+                        * -4    an  error   was   detected   when  calculating
+                                weights/nodes.  N  is  too  large   to  obtain
+                                weights/nodes  with  high   enough   accuracy.
+                                Try  to   use   multiple   precision  version.
+                        * -3    internal eigenproblem solver hasn't  converged
+                        * -1    incorrect N was passed
+                        * +1    OK
+        X           -   array[0..N-1] - array of quadrature nodes,
+                        in ascending order.
+        W           -   array[0..N-1] - array of quadrature weights.
+
+
+      -- ALGLIB --
+         Copyright 12.05.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void gqgenerategausslegendre(int n, out int info, out double[] x, out double[] w)
+    {
+        info = 0;
+        x = new double[0];
+        w = new double[0];
+        gq.gqgenerategausslegendre(n, ref info, ref x, ref w);
+        return;
+    }
+
+    /*************************************************************************
+    Returns  nodes/weights  for  Gauss-Jacobi quadrature on [-1,1] with weight
+    function W(x)=Power(1-x,Alpha)*Power(1+x,Beta).
+
+    INPUT PARAMETERS:
+        N           -   number of nodes, >=1
+        Alpha       -   power-law coefficient, Alpha>-1
+        Beta        -   power-law coefficient, Beta>-1
+
+    OUTPUT PARAMETERS:
+        Info        -   error code:
+                        * -4    an  error  was   detected   when   calculating
+                                weights/nodes. Alpha or  Beta  are  too  close
+                                to -1 to obtain weights/nodes with high enough
+                                accuracy, or, may be, N is too large.  Try  to
+                                use multiple precision version.
+                        * -3    internal eigenproblem solver hasn't converged
+                        * -1    incorrect N/Alpha/Beta was passed
+                        * +1    OK
+        X           -   array[0..N-1] - array of quadrature nodes,
+                        in ascending order.
+        W           -   array[0..N-1] - array of quadrature weights.
+
+
+      -- ALGLIB --
+         Copyright 12.05.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void gqgenerategaussjacobi(int n, double alpha, double beta, out int info, out double[] x, out double[] w)
+    {
+        info = 0;
+        x = new double[0];
+        w = new double[0];
+        gq.gqgenerategaussjacobi(n, alpha, beta, ref info, ref x, ref w);
+        return;
+    }
+
+    /*************************************************************************
+    Returns  nodes/weights  for  Gauss-Laguerre  quadrature  on  [0,+inf) with
+    weight function W(x)=Power(x,Alpha)*Exp(-x)
+
+    INPUT PARAMETERS:
+        N           -   number of nodes, >=1
+        Alpha       -   power-law coefficient, Alpha>-1
+
+    OUTPUT PARAMETERS:
+        Info        -   error code:
+                        * -4    an  error  was   detected   when   calculating
+                                weights/nodes. Alpha is too  close  to  -1  to
+                                obtain weights/nodes with high enough accuracy
+                                or, may  be,  N  is  too  large.  Try  to  use
+                                multiple precision version.
+                        * -3    internal eigenproblem solver hasn't converged
+                        * -1    incorrect N/Alpha was passed
+                        * +1    OK
+        X           -   array[0..N-1] - array of quadrature nodes,
+                        in ascending order.
+        W           -   array[0..N-1] - array of quadrature weights.
+
+
+      -- ALGLIB --
+         Copyright 12.05.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void gqgenerategausslaguerre(int n, double alpha, out int info, out double[] x, out double[] w)
+    {
+        info = 0;
+        x = new double[0];
+        w = new double[0];
+        gq.gqgenerategausslaguerre(n, alpha, ref info, ref x, ref w);
+        return;
+    }
+
+    /*************************************************************************
+    Returns  nodes/weights  for  Gauss-Hermite  quadrature on (-inf,+inf) with
+    weight function W(x)=Exp(-x*x)
+
+    INPUT PARAMETERS:
+        N           -   number of nodes, >=1
+
+    OUTPUT PARAMETERS:
+        Info        -   error code:
+                        * -4    an  error  was   detected   when   calculating
+                                weights/nodes.  May be, N is too large. Try to
+                                use multiple precision version.
+                        * -3    internal eigenproblem solver hasn't converged
+                        * -1    incorrect N/Alpha was passed
+                        * +1    OK
+        X           -   array[0..N-1] - array of quadrature nodes,
+                        in ascending order.
+        W           -   array[0..N-1] - array of quadrature weights.
+
+
+      -- ALGLIB --
+         Copyright 12.05.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void gqgenerategausshermite(int n, out int info, out double[] x, out double[] w)
+    {
+        info = 0;
+        x = new double[0];
+        w = new double[0];
+        gq.gqgenerategausshermite(n, ref info, ref x, ref w);
+        return;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    Computation of nodes and weights of a Gauss-Kronrod quadrature formula
+
+    The algorithm generates the N-point Gauss-Kronrod quadrature formula  with
+    weight  function  given  by  coefficients  alpha  and beta of a recurrence
+    relation which generates a system of orthogonal polynomials:
+
+        P-1(x)   =  0
+        P0(x)    =  1
+        Pn+1(x)  =  (x-alpha(n))*Pn(x)  -  beta(n)*Pn-1(x)
+
+    and zero moment Mu0
+
+        Mu0 = integral(W(x)dx,a,b)
+
+
+    INPUT PARAMETERS:
+        Alpha          alpha coefficients, array[0..floor(3*K/2)].
+        Beta           beta coefficients,  array[0..ceil(3*K/2)].
+                        Beta[0] is not used and may be arbitrary.
+                        Beta[I]>0.
+        Mu0            zeroth moment of the weight function.
+        N              number of nodes of the Gauss-Kronrod quadrature formula,
+                        N >= 3,
+                        N =  2*K+1.
+
+    OUTPUT PARAMETERS:
+        Info        -   error code:
+                        * -5    no real and positive Gauss-Kronrod formula can
+                                be created for such a weight function  with  a
+                                given number of nodes.
+                        * -4    N is too large, task may be ill  conditioned -
+                                x[i]=x[i+1] found.
+                        * -3    internal eigenproblem solver hasn't converged
+                        * -2    Beta[i]<=0
+                        * -1    incorrect N was passed
+                        * +1    OK
+        X           -   array[0..N-1] - array of quadrature nodes,
+                        in ascending order.
+        WKronrod    -   array[0..N-1] - Kronrod weights
+        WGauss      -   array[0..N-1] - Gauss weights (interleaved with zeros
+                        corresponding to extended Kronrod nodes).
+
+      -- ALGLIB --
+         Copyright 08.05.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void gkqgeneraterec(double[] alpha, double[] beta, double mu0, int n, out int info, out double[] x, out double[] wkronrod, out double[] wgauss)
+    {
+        info = 0;
+        x = new double[0];
+        wkronrod = new double[0];
+        wgauss = new double[0];
+        gkq.gkqgeneraterec(alpha, beta, mu0, n, ref info, ref x, ref wkronrod, ref wgauss);
+        return;
+    }
+
+    /*************************************************************************
+    Returns   Gauss   and   Gauss-Kronrod   nodes/weights  for  Gauss-Legendre
+    quadrature with N points.
+
+    GKQLegendreCalc (calculation) or  GKQLegendreTbl  (precomputed  table)  is
+    used depending on machine precision and number of nodes.
+
+    INPUT PARAMETERS:
+        N           -   number of Kronrod nodes, must be odd number, >=3.
+
+    OUTPUT PARAMETERS:
+        Info        -   error code:
+                        * -4    an  error   was   detected   when  calculating
+                                weights/nodes.  N  is  too  large   to  obtain
+                                weights/nodes  with  high   enough   accuracy.
+                                Try  to   use   multiple   precision  version.
+                        * -3    internal eigenproblem solver hasn't converged
+                        * -1    incorrect N was passed
+                        * +1    OK
+        X           -   array[0..N-1] - array of quadrature nodes, ordered in
+                        ascending order.
+        WKronrod    -   array[0..N-1] - Kronrod weights
+        WGauss      -   array[0..N-1] - Gauss weights (interleaved with zeros
+                        corresponding to extended Kronrod nodes).
+
+
+      -- ALGLIB --
+         Copyright 12.05.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void gkqgenerategausslegendre(int n, out int info, out double[] x, out double[] wkronrod, out double[] wgauss)
+    {
+        info = 0;
+        x = new double[0];
+        wkronrod = new double[0];
+        wgauss = new double[0];
+        gkq.gkqgenerategausslegendre(n, ref info, ref x, ref wkronrod, ref wgauss);
+        return;
+    }
+
+    /*************************************************************************
+    Returns   Gauss   and   Gauss-Kronrod   nodes/weights   for   Gauss-Jacobi
+    quadrature on [-1,1] with weight function
+
+        W(x)=Power(1-x,Alpha)*Power(1+x,Beta).
+
+    INPUT PARAMETERS:
+        N           -   number of Kronrod nodes, must be odd number, >=3.
+        Alpha       -   power-law coefficient, Alpha>-1
+        Beta        -   power-law coefficient, Beta>-1
+
+    OUTPUT PARAMETERS:
+        Info        -   error code:
+                        * -5    no real and positive Gauss-Kronrod formula can
+                                be created for such a weight function  with  a
+                                given number of nodes.
+                        * -4    an  error  was   detected   when   calculating
+                                weights/nodes. Alpha or  Beta  are  too  close
+                                to -1 to obtain weights/nodes with high enough
+                                accuracy, or, may be, N is too large.  Try  to
+                                use multiple precision version.
+                        * -3    internal eigenproblem solver hasn't converged
+                        * -1    incorrect N was passed
+                        * +1    OK
+                        * +2    OK, but quadrature rule have exterior  nodes,
+                                x[0]<-1 or x[n-1]>+1
+        X           -   array[0..N-1] - array of quadrature nodes, ordered in
+                        ascending order.
+        WKronrod    -   array[0..N-1] - Kronrod weights
+        WGauss      -   array[0..N-1] - Gauss weights (interleaved with zeros
+                        corresponding to extended Kronrod nodes).
+
+
+      -- ALGLIB --
+         Copyright 12.05.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void gkqgenerategaussjacobi(int n, double alpha, double beta, out int info, out double[] x, out double[] wkronrod, out double[] wgauss)
+    {
+        info = 0;
+        x = new double[0];
+        wkronrod = new double[0];
+        wgauss = new double[0];
+        gkq.gkqgenerategaussjacobi(n, alpha, beta, ref info, ref x, ref wkronrod, ref wgauss);
+        return;
+    }
+
+    /*************************************************************************
+    Returns Gauss and Gauss-Kronrod nodes for quadrature with N points.
+
+    Reduction to tridiagonal eigenproblem is used.
+
+    INPUT PARAMETERS:
+        N           -   number of Kronrod nodes, must be odd number, >=3.
+
+    OUTPUT PARAMETERS:
+        Info        -   error code:
+                        * -4    an  error   was   detected   when  calculating
+                                weights/nodes.  N  is  too  large   to  obtain
+                                weights/nodes  with  high   enough   accuracy.
+                                Try  to   use   multiple   precision  version.
+                        * -3    internal eigenproblem solver hasn't converged
+                        * -1    incorrect N was passed
+                        * +1    OK
+        X           -   array[0..N-1] - array of quadrature nodes, ordered in
+                        ascending order.
+        WKronrod    -   array[0..N-1] - Kronrod weights
+        WGauss      -   array[0..N-1] - Gauss weights (interleaved with zeros
+                        corresponding to extended Kronrod nodes).
+
+      -- ALGLIB --
+         Copyright 12.05.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void gkqlegendrecalc(int n, out int info, out double[] x, out double[] wkronrod, out double[] wgauss)
+    {
+        info = 0;
+        x = new double[0];
+        wkronrod = new double[0];
+        wgauss = new double[0];
+        gkq.gkqlegendrecalc(n, ref info, ref x, ref wkronrod, ref wgauss);
+        return;
+    }
+
+    /*************************************************************************
+    Returns Gauss and Gauss-Kronrod nodes for quadrature with N  points  using
+    pre-calculated table. Nodes/weights were  computed  with  accuracy  up  to
+    1.0E-32 (if MPFR version of ALGLIB is used). In standard double  precision
+    accuracy reduces to something about 2.0E-16 (depending  on your compiler's
+    handling of long floating point constants).
+
+    INPUT PARAMETERS:
+        N           -   number of Kronrod nodes.
+                        N can be 15, 21, 31, 41, 51, 61.
+
+    OUTPUT PARAMETERS:
+        X           -   array[0..N-1] - array of quadrature nodes, ordered in
+                        ascending order.
+        WKronrod    -   array[0..N-1] - Kronrod weights
+        WGauss      -   array[0..N-1] - Gauss weights (interleaved with zeros
+                        corresponding to extended Kronrod nodes).
+
+
+      -- ALGLIB --
+         Copyright 12.05.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void gkqlegendretbl(int n, out double[] x, out double[] wkronrod, out double[] wgauss, out double eps)
+    {
+        x = new double[0];
+        wkronrod = new double[0];
+        wgauss = new double[0];
+        eps = 0;
+        gkq.gkqlegendretbl(n, ref x, ref wkronrod, ref wgauss, ref eps);
+        return;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    Integration report:
+    * TerminationType = completetion code:
+        * -5    non-convergence of Gauss-Kronrod nodes
+                calculation subroutine.
+        * -1    incorrect parameters were specified
+        *  1    OK
+    * Rep.NFEV countains number of function calculations
+    * Rep.NIntervals contains number of intervals [a,b]
+      was partitioned into.
+    *************************************************************************/
+    public class autogkreport
+    {
+        //
+        // Public declarations
+        //
+        public int terminationtype { get { return _innerobj.terminationtype; } set { _innerobj.terminationtype = value; } }
+        public int nfev { get { return _innerobj.nfev; } set { _innerobj.nfev = value; } }
+        public int nintervals { get { return _innerobj.nintervals; } set { _innerobj.nintervals = value; } }
+
+        public autogkreport()
+        {
+            _innerobj = new autogk.autogkreport();
+        }
+
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private autogk.autogkreport _innerobj;
+        public autogk.autogkreport innerobj { get { return _innerobj; } }
+        public autogkreport(autogk.autogkreport obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+
+    /*************************************************************************
+    This structure stores state of the integration algorithm.
+
+    Although this class has public fields,  they are not intended for external
+    use. You should use ALGLIB functions to work with this class:
+    * autogksmooth()/AutoGKSmoothW()/... to create objects
+    * autogkintegrate() to begin integration
+    * autogkresults() to get results
+    *************************************************************************/
+    public class autogkstate
+    {
+        //
+        // Public declarations
+        //
+        public bool needf { get { return _innerobj.needf; } set { _innerobj.needf = value; } }
+        public double x { get { return _innerobj.x; } set { _innerobj.x = value; } }
+        public double xminusa { get { return _innerobj.xminusa; } set { _innerobj.xminusa = value; } }
+        public double bminusx { get { return _innerobj.bminusx; } set { _innerobj.bminusx = value; } }
+        public double f { get { return _innerobj.f; } set { _innerobj.f = value; } }
+
+        public autogkstate()
+        {
+            _innerobj = new autogk.autogkstate();
+        }
+
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private autogk.autogkstate _innerobj;
+        public autogk.autogkstate innerobj { get { return _innerobj; } }
+        public autogkstate(autogk.autogkstate obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+    /*************************************************************************
+    Integration of a smooth function F(x) on a finite interval [a,b].
+
+    Fast-convergent algorithm based on a Gauss-Kronrod formula is used. Result
+    is calculated with accuracy close to the machine precision.
+
+    Algorithm works well only with smooth integrands.  It  may  be  used  with
+    continuous non-smooth integrands, but with  less  performance.
+
+    It should never be used with integrands which have integrable singularities
+    at lower or upper limits - algorithm may crash. Use AutoGKSingular in such
+    cases.
+
+    INPUT PARAMETERS:
+        A, B    -   interval boundaries (A<B, A=B or A>B)
+
+    OUTPUT PARAMETERS
+        State   -   structure which stores algorithm state
+
+    SEE ALSO
+        AutoGKSmoothW, AutoGKSingular, AutoGKResults.
+
+
+      -- ALGLIB --
+         Copyright 06.05.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void autogksmooth(double a, double b, out autogkstate state)
+    {
+        state = new autogkstate();
+        autogk.autogksmooth(a, b, state.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    Integration of a smooth function F(x) on a finite interval [a,b].
+
+    This subroutine is same as AutoGKSmooth(), but it guarantees that interval
+    [a,b] is partitioned into subintervals which have width at most XWidth.
+
+    Subroutine  can  be  used  when  integrating nearly-constant function with
+    narrow "bumps" (about XWidth wide). If "bumps" are too narrow, AutoGKSmooth
+    subroutine can overlook them.
+
+    INPUT PARAMETERS:
+        A, B    -   interval boundaries (A<B, A=B or A>B)
+
+    OUTPUT PARAMETERS
+        State   -   structure which stores algorithm state
+
+    SEE ALSO
+        AutoGKSmooth, AutoGKSingular, AutoGKResults.
+
+
+      -- ALGLIB --
+         Copyright 06.05.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void autogksmoothw(double a, double b, double xwidth, out autogkstate state)
+    {
+        state = new autogkstate();
+        autogk.autogksmoothw(a, b, xwidth, state.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    Integration on a finite interval [A,B].
+    Integrand have integrable singularities at A/B.
+
+    F(X) must diverge as "(x-A)^alpha" at A, as "(B-x)^beta" at B,  with known
+    alpha/beta (alpha>-1, beta>-1).  If alpha/beta  are  not known,  estimates
+    from below can be used (but these estimates should be greater than -1 too).
+
+    One  of  alpha/beta variables (or even both alpha/beta) may be equal to 0,
+    which means than function F(x) is non-singular at A/B. Anyway (singular at
+    bounds or not), function F(x) is supposed to be continuous on (A,B).
+
+    Fast-convergent algorithm based on a Gauss-Kronrod formula is used. Result
+    is calculated with accuracy close to the machine precision.
+
+    INPUT PARAMETERS:
+        A, B    -   interval boundaries (A<B, A=B or A>B)
+        Alpha   -   power-law coefficient of the F(x) at A,
+                    Alpha>-1
+        Beta    -   power-law coefficient of the F(x) at B,
+                    Beta>-1
+
+    OUTPUT PARAMETERS
+        State   -   structure which stores algorithm state
+
+    SEE ALSO
+        AutoGKSmooth, AutoGKSmoothW, AutoGKResults.
+
+
+      -- ALGLIB --
+         Copyright 06.05.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void autogksingular(double a, double b, double alpha, double beta, out autogkstate state)
+    {
+        state = new autogkstate();
+        autogk.autogksingular(a, b, alpha, beta, state.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    This function provides reverse communication interface
+    Reverse communication interface is not documented or recommended to use.
+    See below for functions which provide better documented API
+    *************************************************************************/
+    public static bool autogkiteration(autogkstate state)
+    {
+
+        bool result = autogk.autogkiteration(state.innerobj);
+        return result;
+    }
+
+
+    /*************************************************************************
+    This function is used to launcn iterations of ODE solver
+
+    It accepts following parameters:
+        diff    -   callback which calculates dy/dx for given y and x
+        obj     -   optional object which is passed to diff; can be NULL
+
+
+      -- ALGLIB --
+         Copyright 07.05.2009 by Bochkanov Sergey
+
+    *************************************************************************/
+    public static void autogkintegrate(autogkstate state, integrator1_func func, object obj)
+    {
+        if( func==null )
+            throw new alglibexception("ALGLIB: error in 'autogkintegrate()' (func is null)");
+        while( alglib.autogkiteration(state) )
+        {
+            if( state.needf )
+            {
+                func(state.innerobj.x, state.innerobj.xminusa, state.innerobj.bminusx, ref state.innerobj.f, obj);
+                continue;
+            }
+            throw new alglibexception("ALGLIB: unexpected error in 'autogksolve'");
+        }
+    }
+
+    /*************************************************************************
+    Adaptive integration results
+
+    Called after AutoGKIteration returned False.
+
+    Input parameters:
+        State   -   algorithm state (used by AutoGKIteration).
+
+    Output parameters:
+        V       -   integral(f(x)dx,a,b)
+        Rep     -   optimization report (see AutoGKReport description)
+
+      -- ALGLIB --
+         Copyright 14.11.2007 by Bochkanov Sergey
+    *************************************************************************/
+    public static void autogkresults(autogkstate state, out double v, out autogkreport rep)
+    {
+        v = 0;
+        rep = new autogkreport();
+        autogk.autogkresults(state.innerobj, ref v, rep.innerobj);
+        return;
+    }
+
+}
+public partial class alglib
+{
+    public class gq
+    {
+        /*************************************************************************
+        Computation of nodes and weights for a Gauss quadrature formula
+
+        The algorithm generates the N-point Gauss quadrature formula  with  weight
+        function given by coefficients alpha and beta  of  a  recurrence  relation
+        which generates a system of orthogonal polynomials:
+
+        P-1(x)   =  0
+        P0(x)    =  1
+        Pn+1(x)  =  (x-alpha(n))*Pn(x)  -  beta(n)*Pn-1(x)
+
+        and zeroth moment Mu0
+
+        Mu0 = integral(W(x)dx,a,b)
+
+        INPUT PARAMETERS:
+            Alpha      array[0..N-1], alpha coefficients
+            Beta       array[0..N-1], beta coefficients
+                        Zero-indexed element is not used and may be arbitrary.
+                        Beta[I]>0.
+            Mu0        zeroth moment of the weight function.
+            N          number of nodes of the quadrature formula, N>=1
+
+        OUTPUT PARAMETERS:
+            Info    -   error code:
+                        * -3    internal eigenproblem solver hasn't converged
+                        * -2    Beta[i]<=0
+                        * -1    incorrect N was passed
+                        *  1    OK
+            X       -   array[0..N-1] - array of quadrature nodes,
+                        in ascending order.
+            W       -   array[0..N-1] - array of quadrature weights.
+
+          -- ALGLIB --
+             Copyright 2005-2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void gqgeneraterec(double[] alpha,
+            double[] beta,
+            double mu0,
+            int n,
+            ref int info,
+            ref double[] x,
+            ref double[] w)
+        {
+            int i = 0;
+            double[] d = new double[0];
+            double[] e = new double[0];
+            double[,] z = new double[0,0];
+
+            info = 0;
+            x = new double[0];
+            w = new double[0];
+
+            if( n<1 )
+            {
+                info = -1;
+                return;
+            }
+            info = 1;
+            
+            //
+            // Initialize
+            //
+            d = new double[n];
+            e = new double[n];
+            for(i=1; i<=n-1; i++)
+            {
+                d[i-1] = alpha[i-1];
+                if( (double)(beta[i])<=(double)(0) )
+                {
+                    info = -2;
+                    return;
+                }
+                e[i-1] = Math.Sqrt(beta[i]);
+            }
+            d[n-1] = alpha[n-1];
+            
+            //
+            // EVD
+            //
+            if( !evd.smatrixtdevd(ref d, e, n, 3, ref z) )
+            {
+                info = -3;
+                return;
+            }
+            
+            //
+            // Generate
+            //
+            x = new double[n];
+            w = new double[n];
+            for(i=1; i<=n; i++)
+            {
+                x[i-1] = d[i-1];
+                w[i-1] = mu0*math.sqr(z[0,i-1]);
+            }
+        }
+
+
+        /*************************************************************************
+        Computation of nodes and weights for a Gauss-Lobatto quadrature formula
+
+        The algorithm generates the N-point Gauss-Lobatto quadrature formula  with
+        weight function given by coefficients alpha and beta of a recurrence which
+        generates a system of orthogonal polynomials.
+
+        P-1(x)   =  0
+        P0(x)    =  1
+        Pn+1(x)  =  (x-alpha(n))*Pn(x)  -  beta(n)*Pn-1(x)
+
+        and zeroth moment Mu0
+
+        Mu0 = integral(W(x)dx,a,b)
+
+        INPUT PARAMETERS:
+            Alpha      array[0..N-2], alpha coefficients
+            Beta       array[0..N-2], beta coefficients.
+                        Zero-indexed element is not used, may be arbitrary.
+                        Beta[I]>0
+            Mu0        zeroth moment of the weighting function.
+            A          left boundary of the integration interval.
+            B          right boundary of the integration interval.
+            N          number of nodes of the quadrature formula, N>=3
+                        (including the left and right boundary nodes).
+
+        OUTPUT PARAMETERS:
+            Info    -   error code:
+                        * -3    internal eigenproblem solver hasn't converged
+                        * -2    Beta[i]<=0
+                        * -1    incorrect N was passed
+                        *  1    OK
+            X       -   array[0..N-1] - array of quadrature nodes,
+                        in ascending order.
+            W       -   array[0..N-1] - array of quadrature weights.
+
+          -- ALGLIB --
+             Copyright 2005-2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void gqgenerategausslobattorec(double[] alpha,
+            double[] beta,
+            double mu0,
+            double a,
+            double b,
+            int n,
+            ref int info,
+            ref double[] x,
+            ref double[] w)
+        {
+            int i = 0;
+            double[] d = new double[0];
+            double[] e = new double[0];
+            double[,] z = new double[0,0];
+            double pim1a = 0;
+            double pia = 0;
+            double pim1b = 0;
+            double pib = 0;
+            double t = 0;
+            double a11 = 0;
+            double a12 = 0;
+            double a21 = 0;
+            double a22 = 0;
+            double b1 = 0;
+            double b2 = 0;
+            double alph = 0;
+            double bet = 0;
+
+            alpha = (double[])alpha.Clone();
+            beta = (double[])beta.Clone();
+            info = 0;
+            x = new double[0];
+            w = new double[0];
+
+            if( n<=2 )
+            {
+                info = -1;
+                return;
+            }
+            info = 1;
+            
+            //
+            // Initialize, D[1:N+1], E[1:N]
+            //
+            n = n-2;
+            d = new double[n+2];
+            e = new double[n+1];
+            for(i=1; i<=n+1; i++)
+            {
+                d[i-1] = alpha[i-1];
+            }
+            for(i=1; i<=n; i++)
+            {
+                if( (double)(beta[i])<=(double)(0) )
+                {
+                    info = -2;
+                    return;
+                }
+                e[i-1] = Math.Sqrt(beta[i]);
+            }
+            
+            //
+            // Caclulate Pn(a), Pn+1(a), Pn(b), Pn+1(b)
+            //
+            beta[0] = 0;
+            pim1a = 0;
+            pia = 1;
+            pim1b = 0;
+            pib = 1;
+            for(i=1; i<=n+1; i++)
+            {
+                
+                //
+                // Pi(a)
+                //
+                t = (a-alpha[i-1])*pia-beta[i-1]*pim1a;
+                pim1a = pia;
+                pia = t;
+                
+                //
+                // Pi(b)
+                //
+                t = (b-alpha[i-1])*pib-beta[i-1]*pim1b;
+                pim1b = pib;
+                pib = t;
+            }
+            
+            //
+            // Calculate alpha'(n+1), beta'(n+1)
+            //
+            a11 = pia;
+            a12 = pim1a;
+            a21 = pib;
+            a22 = pim1b;
+            b1 = a*pia;
+            b2 = b*pib;
+            if( (double)(Math.Abs(a11))>(double)(Math.Abs(a21)) )
+            {
+                a22 = a22-a12*a21/a11;
+                b2 = b2-b1*a21/a11;
+                bet = b2/a22;
+                alph = (b1-bet*a12)/a11;
+            }
+            else
+            {
+                a12 = a12-a22*a11/a21;
+                b1 = b1-b2*a11/a21;
+                bet = b1/a12;
+                alph = (b2-bet*a22)/a21;
+            }
+            if( (double)(bet)<(double)(0) )
+            {
+                info = -3;
+                return;
+            }
+            d[n+1] = alph;
+            e[n] = Math.Sqrt(bet);
+            
+            //
+            // EVD
+            //
+            if( !evd.smatrixtdevd(ref d, e, n+2, 3, ref z) )
+            {
+                info = -3;
+                return;
+            }
+            
+            //
+            // Generate
+            //
+            x = new double[n+2];
+            w = new double[n+2];
+            for(i=1; i<=n+2; i++)
+            {
+                x[i-1] = d[i-1];
+                w[i-1] = mu0*math.sqr(z[0,i-1]);
+            }
+        }
+
+
+        /*************************************************************************
+        Computation of nodes and weights for a Gauss-Radau quadrature formula
+
+        The algorithm generates the N-point Gauss-Radau  quadrature  formula  with
+        weight function given by the coefficients alpha and  beta  of a recurrence
+        which generates a system of orthogonal polynomials.
+
+        P-1(x)   =  0
+        P0(x)    =  1
+        Pn+1(x)  =  (x-alpha(n))*Pn(x)  -  beta(n)*Pn-1(x)
+
+        and zeroth moment Mu0
+
+        Mu0 = integral(W(x)dx,a,b)
+
+        INPUT PARAMETERS:
+            Alpha      array[0..N-2], alpha coefficients.
+            Beta       array[0..N-1], beta coefficients
+                        Zero-indexed element is not used.
+                        Beta[I]>0
+            Mu0        zeroth moment of the weighting function.
+            A          left boundary of the integration interval.
+            N          number of nodes of the quadrature formula, N>=2
+                        (including the left boundary node).
+
+        OUTPUT PARAMETERS:
+            Info    -   error code:
+                        * -3    internal eigenproblem solver hasn't converged
+                        * -2    Beta[i]<=0
+                        * -1    incorrect N was passed
+                        *  1    OK
+            X       -   array[0..N-1] - array of quadrature nodes,
+                        in ascending order.
+            W       -   array[0..N-1] - array of quadrature weights.
+
+
+          -- ALGLIB --
+             Copyright 2005-2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void gqgenerategaussradaurec(double[] alpha,
+            double[] beta,
+            double mu0,
+            double a,
+            int n,
+            ref int info,
+            ref double[] x,
+            ref double[] w)
+        {
+            int i = 0;
+            double[] d = new double[0];
+            double[] e = new double[0];
+            double[,] z = new double[0,0];
+            double polim1 = 0;
+            double poli = 0;
+            double t = 0;
+
+            alpha = (double[])alpha.Clone();
+            beta = (double[])beta.Clone();
+            info = 0;
+            x = new double[0];
+            w = new double[0];
+
+            if( n<2 )
+            {
+                info = -1;
+                return;
+            }
+            info = 1;
+            
+            //
+            // Initialize, D[1:N], E[1:N]
+            //
+            n = n-1;
+            d = new double[n+1];
+            e = new double[n];
+            for(i=1; i<=n; i++)
+            {
+                d[i-1] = alpha[i-1];
+                if( (double)(beta[i])<=(double)(0) )
+                {
+                    info = -2;
+                    return;
+                }
+                e[i-1] = Math.Sqrt(beta[i]);
+            }
+            
+            //
+            // Caclulate Pn(a), Pn-1(a), and D[N+1]
+            //
+            beta[0] = 0;
+            polim1 = 0;
+            poli = 1;
+            for(i=1; i<=n; i++)
+            {
+                t = (a-alpha[i-1])*poli-beta[i-1]*polim1;
+                polim1 = poli;
+                poli = t;
+            }
+            d[n] = a-beta[n]*polim1/poli;
+            
+            //
+            // EVD
+            //
+            if( !evd.smatrixtdevd(ref d, e, n+1, 3, ref z) )
+            {
+                info = -3;
+                return;
+            }
+            
+            //
+            // Generate
+            //
+            x = new double[n+1];
+            w = new double[n+1];
+            for(i=1; i<=n+1; i++)
+            {
+                x[i-1] = d[i-1];
+                w[i-1] = mu0*math.sqr(z[0,i-1]);
+            }
+        }
+
+
+        /*************************************************************************
+        Returns nodes/weights for Gauss-Legendre quadrature on [-1,1] with N
+        nodes.
+
+        INPUT PARAMETERS:
+            N           -   number of nodes, >=1
+
+        OUTPUT PARAMETERS:
+            Info        -   error code:
+                            * -4    an  error   was   detected   when  calculating
+                                    weights/nodes.  N  is  too  large   to  obtain
+                                    weights/nodes  with  high   enough   accuracy.
+                                    Try  to   use   multiple   precision  version.
+                            * -3    internal eigenproblem solver hasn't  converged
+                            * -1    incorrect N was passed
+                            * +1    OK
+            X           -   array[0..N-1] - array of quadrature nodes,
+                            in ascending order.
+            W           -   array[0..N-1] - array of quadrature weights.
+
+
+          -- ALGLIB --
+             Copyright 12.05.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void gqgenerategausslegendre(int n,
+            ref int info,
+            ref double[] x,
+            ref double[] w)
+        {
+            double[] alpha = new double[0];
+            double[] beta = new double[0];
+            int i = 0;
+
+            info = 0;
+            x = new double[0];
+            w = new double[0];
+
+            if( n<1 )
+            {
+                info = -1;
+                return;
+            }
+            alpha = new double[n];
+            beta = new double[n];
+            for(i=0; i<=n-1; i++)
+            {
+                alpha[i] = 0;
+            }
+            beta[0] = 2;
+            for(i=1; i<=n-1; i++)
+            {
+                beta[i] = 1/(4-1/math.sqr(i));
+            }
+            gqgeneraterec(alpha, beta, beta[0], n, ref info, ref x, ref w);
+            
+            //
+            // test basic properties to detect errors
+            //
+            if( info>0 )
+            {
+                if( (double)(x[0])<(double)(-1) || (double)(x[n-1])>(double)(1) )
+                {
+                    info = -4;
+                }
+                for(i=0; i<=n-2; i++)
+                {
+                    if( (double)(x[i])>=(double)(x[i+1]) )
+                    {
+                        info = -4;
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Returns  nodes/weights  for  Gauss-Jacobi quadrature on [-1,1] with weight
+        function W(x)=Power(1-x,Alpha)*Power(1+x,Beta).
+
+        INPUT PARAMETERS:
+            N           -   number of nodes, >=1
+            Alpha       -   power-law coefficient, Alpha>-1
+            Beta        -   power-law coefficient, Beta>-1
+
+        OUTPUT PARAMETERS:
+            Info        -   error code:
+                            * -4    an  error  was   detected   when   calculating
+                                    weights/nodes. Alpha or  Beta  are  too  close
+                                    to -1 to obtain weights/nodes with high enough
+                                    accuracy, or, may be, N is too large.  Try  to
+                                    use multiple precision version.
+                            * -3    internal eigenproblem solver hasn't converged
+                            * -1    incorrect N/Alpha/Beta was passed
+                            * +1    OK
+            X           -   array[0..N-1] - array of quadrature nodes,
+                            in ascending order.
+            W           -   array[0..N-1] - array of quadrature weights.
+
+
+          -- ALGLIB --
+             Copyright 12.05.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void gqgenerategaussjacobi(int n,
+            double alpha,
+            double beta,
+            ref int info,
+            ref double[] x,
+            ref double[] w)
+        {
+            double[] a = new double[0];
+            double[] b = new double[0];
+            double alpha2 = 0;
+            double beta2 = 0;
+            double apb = 0;
+            double t = 0;
+            int i = 0;
+            double s = 0;
+
+            info = 0;
+            x = new double[0];
+            w = new double[0];
+
+            if( (n<1 || (double)(alpha)<=(double)(-1)) || (double)(beta)<=(double)(-1) )
+            {
+                info = -1;
+                return;
+            }
+            a = new double[n];
+            b = new double[n];
+            apb = alpha+beta;
+            a[0] = (beta-alpha)/(apb+2);
+            t = (apb+1)*Math.Log(2)+gammafunc.lngamma(alpha+1, ref s)+gammafunc.lngamma(beta+1, ref s)-gammafunc.lngamma(apb+2, ref s);
+            if( (double)(t)>(double)(Math.Log(math.maxrealnumber)) )
+            {
+                info = -4;
+                return;
+            }
+            b[0] = Math.Exp(t);
+            if( n>1 )
+            {
+                alpha2 = math.sqr(alpha);
+                beta2 = math.sqr(beta);
+                a[1] = (beta2-alpha2)/((apb+2)*(apb+4));
+                b[1] = 4*(alpha+1)*(beta+1)/((apb+3)*math.sqr(apb+2));
+                for(i=2; i<=n-1; i++)
+                {
+                    a[i] = 0.25*(beta2-alpha2)/(i*i*(1+0.5*apb/i)*(1+0.5*(apb+2)/i));
+                    b[i] = 0.25*(1+alpha/i)*(1+beta/i)*(1+apb/i)/((1+0.5*(apb+1)/i)*(1+0.5*(apb-1)/i)*math.sqr(1+0.5*apb/i));
+                }
+            }
+            gqgeneraterec(a, b, b[0], n, ref info, ref x, ref w);
+            
+            //
+            // test basic properties to detect errors
+            //
+            if( info>0 )
+            {
+                if( (double)(x[0])<(double)(-1) || (double)(x[n-1])>(double)(1) )
+                {
+                    info = -4;
+                }
+                for(i=0; i<=n-2; i++)
+                {
+                    if( (double)(x[i])>=(double)(x[i+1]) )
+                    {
+                        info = -4;
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Returns  nodes/weights  for  Gauss-Laguerre  quadrature  on  [0,+inf) with
+        weight function W(x)=Power(x,Alpha)*Exp(-x)
+
+        INPUT PARAMETERS:
+            N           -   number of nodes, >=1
+            Alpha       -   power-law coefficient, Alpha>-1
+
+        OUTPUT PARAMETERS:
+            Info        -   error code:
+                            * -4    an  error  was   detected   when   calculating
+                                    weights/nodes. Alpha is too  close  to  -1  to
+                                    obtain weights/nodes with high enough accuracy
+                                    or, may  be,  N  is  too  large.  Try  to  use
+                                    multiple precision version.
+                            * -3    internal eigenproblem solver hasn't converged
+                            * -1    incorrect N/Alpha was passed
+                            * +1    OK
+            X           -   array[0..N-1] - array of quadrature nodes,
+                            in ascending order.
+            W           -   array[0..N-1] - array of quadrature weights.
+
+
+          -- ALGLIB --
+             Copyright 12.05.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void gqgenerategausslaguerre(int n,
+            double alpha,
+            ref int info,
+            ref double[] x,
+            ref double[] w)
+        {
+            double[] a = new double[0];
+            double[] b = new double[0];
+            double t = 0;
+            int i = 0;
+            double s = 0;
+
+            info = 0;
+            x = new double[0];
+            w = new double[0];
+
+            if( n<1 || (double)(alpha)<=(double)(-1) )
+            {
+                info = -1;
+                return;
+            }
+            a = new double[n];
+            b = new double[n];
+            a[0] = alpha+1;
+            t = gammafunc.lngamma(alpha+1, ref s);
+            if( (double)(t)>=(double)(Math.Log(math.maxrealnumber)) )
+            {
+                info = -4;
+                return;
+            }
+            b[0] = Math.Exp(t);
+            if( n>1 )
+            {
+                for(i=1; i<=n-1; i++)
+                {
+                    a[i] = 2*i+alpha+1;
+                    b[i] = i*(i+alpha);
+                }
+            }
+            gqgeneraterec(a, b, b[0], n, ref info, ref x, ref w);
+            
+            //
+            // test basic properties to detect errors
+            //
+            if( info>0 )
+            {
+                if( (double)(x[0])<(double)(0) )
+                {
+                    info = -4;
+                }
+                for(i=0; i<=n-2; i++)
+                {
+                    if( (double)(x[i])>=(double)(x[i+1]) )
+                    {
+                        info = -4;
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Returns  nodes/weights  for  Gauss-Hermite  quadrature on (-inf,+inf) with
+        weight function W(x)=Exp(-x*x)
+
+        INPUT PARAMETERS:
+            N           -   number of nodes, >=1
+
+        OUTPUT PARAMETERS:
+            Info        -   error code:
+                            * -4    an  error  was   detected   when   calculating
+                                    weights/nodes.  May be, N is too large. Try to
+                                    use multiple precision version.
+                            * -3    internal eigenproblem solver hasn't converged
+                            * -1    incorrect N/Alpha was passed
+                            * +1    OK
+            X           -   array[0..N-1] - array of quadrature nodes,
+                            in ascending order.
+            W           -   array[0..N-1] - array of quadrature weights.
+
+
+          -- ALGLIB --
+             Copyright 12.05.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void gqgenerategausshermite(int n,
+            ref int info,
+            ref double[] x,
+            ref double[] w)
+        {
+            double[] a = new double[0];
+            double[] b = new double[0];
+            int i = 0;
+
+            info = 0;
+            x = new double[0];
+            w = new double[0];
+
+            if( n<1 )
+            {
+                info = -1;
+                return;
+            }
+            a = new double[n];
+            b = new double[n];
+            for(i=0; i<=n-1; i++)
+            {
+                a[i] = 0;
+            }
+            b[0] = Math.Sqrt(4*Math.Atan(1));
+            if( n>1 )
+            {
+                for(i=1; i<=n-1; i++)
+                {
+                    b[i] = 0.5*i;
+                }
+            }
+            gqgeneraterec(a, b, b[0], n, ref info, ref x, ref w);
+            
+            //
+            // test basic properties to detect errors
+            //
+            if( info>0 )
+            {
+                for(i=0; i<=n-2; i++)
+                {
+                    if( (double)(x[i])>=(double)(x[i+1]) )
+                    {
+                        info = -4;
+                    }
+                }
+            }
+        }
+
+
+    }
+    public class gkq
+    {
+        /*************************************************************************
+        Computation of nodes and weights of a Gauss-Kronrod quadrature formula
+
+        The algorithm generates the N-point Gauss-Kronrod quadrature formula  with
+        weight  function  given  by  coefficients  alpha  and beta of a recurrence
+        relation which generates a system of orthogonal polynomials:
+
+            P-1(x)   =  0
+            P0(x)    =  1
+            Pn+1(x)  =  (x-alpha(n))*Pn(x)  -  beta(n)*Pn-1(x)
+
+        and zero moment Mu0
+
+            Mu0 = integral(W(x)dx,a,b)
+
+
+        INPUT PARAMETERS:
+            Alpha          alpha coefficients, array[0..floor(3*K/2)].
+            Beta           beta coefficients,  array[0..ceil(3*K/2)].
+                            Beta[0] is not used and may be arbitrary.
+                            Beta[I]>0.
+            Mu0            zeroth moment of the weight function.
+            N              number of nodes of the Gauss-Kronrod quadrature formula,
+                            N >= 3,
+                            N =  2*K+1.
+
+        OUTPUT PARAMETERS:
+            Info        -   error code:
+                            * -5    no real and positive Gauss-Kronrod formula can
+                                    be created for such a weight function  with  a
+                                    given number of nodes.
+                            * -4    N is too large, task may be ill  conditioned -
+                                    x[i]=x[i+1] found.
+                            * -3    internal eigenproblem solver hasn't converged
+                            * -2    Beta[i]<=0
+                            * -1    incorrect N was passed
+                            * +1    OK
+            X           -   array[0..N-1] - array of quadrature nodes,
+                            in ascending order.
+            WKronrod    -   array[0..N-1] - Kronrod weights
+            WGauss      -   array[0..N-1] - Gauss weights (interleaved with zeros
+                            corresponding to extended Kronrod nodes).
+
+          -- ALGLIB --
+             Copyright 08.05.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void gkqgeneraterec(double[] alpha,
+            double[] beta,
+            double mu0,
+            int n,
+            ref int info,
+            ref double[] x,
+            ref double[] wkronrod,
+            ref double[] wgauss)
+        {
+            double[] ta = new double[0];
+            int i = 0;
+            int j = 0;
+            double[] t = new double[0];
+            double[] s = new double[0];
+            int wlen = 0;
+            int woffs = 0;
+            double u = 0;
+            int m = 0;
+            int l = 0;
+            int k = 0;
+            double[] xgtmp = new double[0];
+            double[] wgtmp = new double[0];
+            int i_ = 0;
+
+            alpha = (double[])alpha.Clone();
+            beta = (double[])beta.Clone();
+            info = 0;
+            x = new double[0];
+            wkronrod = new double[0];
+            wgauss = new double[0];
+
+            if( n%2!=1 || n<3 )
+            {
+                info = -1;
+                return;
+            }
+            for(i=0; i<=(int)Math.Ceiling((double)(3*(n/2))/(double)2); i++)
+            {
+                if( (double)(beta[i])<=(double)(0) )
+                {
+                    info = -2;
+                    return;
+                }
+            }
+            info = 1;
+            
+            //
+            // from external conventions about N/Beta/Mu0 to internal
+            //
+            n = n/2;
+            beta[0] = mu0;
+            
+            //
+            // Calculate Gauss nodes/weights, save them for later processing
+            //
+            gq.gqgeneraterec(alpha, beta, mu0, n, ref info, ref xgtmp, ref wgtmp);
+            if( info<0 )
+            {
+                return;
+            }
+            
+            //
+            // Resize:
+            // * A from 0..floor(3*n/2) to 0..2*n
+            // * B from 0..ceil(3*n/2)  to 0..2*n
+            //
+            ta = new double[(int)Math.Floor((double)(3*n)/(double)2)+1];
+            for(i_=0; i_<=(int)Math.Floor((double)(3*n)/(double)2);i_++)
+            {
+                ta[i_] = alpha[i_];
+            }
+            alpha = new double[2*n+1];
+            for(i_=0; i_<=(int)Math.Floor((double)(3*n)/(double)2);i_++)
+            {
+                alpha[i_] = ta[i_];
+            }
+            for(i=(int)Math.Floor((double)(3*n)/(double)2)+1; i<=2*n; i++)
+            {
+                alpha[i] = 0;
+            }
+            ta = new double[(int)Math.Ceiling((double)(3*n)/(double)2)+1];
+            for(i_=0; i_<=(int)Math.Ceiling((double)(3*n)/(double)2);i_++)
+            {
+                ta[i_] = beta[i_];
+            }
+            beta = new double[2*n+1];
+            for(i_=0; i_<=(int)Math.Ceiling((double)(3*n)/(double)2);i_++)
+            {
+                beta[i_] = ta[i_];
+            }
+            for(i=(int)Math.Ceiling((double)(3*n)/(double)2)+1; i<=2*n; i++)
+            {
+                beta[i] = 0;
+            }
+            
+            //
+            // Initialize T, S
+            //
+            wlen = 2+n/2;
+            t = new double[wlen];
+            s = new double[wlen];
+            ta = new double[wlen];
+            woffs = 1;
+            for(i=0; i<=wlen-1; i++)
+            {
+                t[i] = 0;
+                s[i] = 0;
+            }
+            
+            //
+            // Algorithm from Dirk P. Laurie, "Calculation of Gauss-Kronrod quadrature rules", 1997.
+            //
+            t[woffs+0] = beta[n+1];
+            for(m=0; m<=n-2; m++)
+            {
+                u = 0;
+                for(k=(m+1)/2; k>=0; k--)
+                {
+                    l = m-k;
+                    u = u+(alpha[k+n+1]-alpha[l])*t[woffs+k]+beta[k+n+1]*s[woffs+k-1]-beta[l]*s[woffs+k];
+                    s[woffs+k] = u;
+                }
+                for(i_=0; i_<=wlen-1;i_++)
+                {
+                    ta[i_] = t[i_];
+                }
+                for(i_=0; i_<=wlen-1;i_++)
+                {
+                    t[i_] = s[i_];
+                }
+                for(i_=0; i_<=wlen-1;i_++)
+                {
+                    s[i_] = ta[i_];
+                }
+            }
+            for(j=n/2; j>=0; j--)
+            {
+                s[woffs+j] = s[woffs+j-1];
+            }
+            for(m=n-1; m<=2*n-3; m++)
+            {
+                u = 0;
+                for(k=m+1-n; k<=(m-1)/2; k++)
+                {
+                    l = m-k;
+                    j = n-1-l;
+                    u = u-(alpha[k+n+1]-alpha[l])*t[woffs+j]-beta[k+n+1]*s[woffs+j]+beta[l]*s[woffs+j+1];
+                    s[woffs+j] = u;
+                }
+                if( m%2==0 )
+                {
+                    k = m/2;
+                    alpha[k+n+1] = alpha[k]+(s[woffs+j]-beta[k+n+1]*s[woffs+j+1])/t[woffs+j+1];
+                }
+                else
+                {
+                    k = (m+1)/2;
+                    beta[k+n+1] = s[woffs+j]/s[woffs+j+1];
+                }
+                for(i_=0; i_<=wlen-1;i_++)
+                {
+                    ta[i_] = t[i_];
+                }
+                for(i_=0; i_<=wlen-1;i_++)
+                {
+                    t[i_] = s[i_];
+                }
+                for(i_=0; i_<=wlen-1;i_++)
+                {
+                    s[i_] = ta[i_];
+                }
+            }
+            alpha[2*n] = alpha[n-1]-beta[2*n]*s[woffs+0]/t[woffs+0];
+            
+            //
+            // calculation of Kronrod nodes and weights, unpacking of Gauss weights
+            //
+            gq.gqgeneraterec(alpha, beta, mu0, 2*n+1, ref info, ref x, ref wkronrod);
+            if( info==-2 )
+            {
+                info = -5;
+            }
+            if( info<0 )
+            {
+                return;
+            }
+            for(i=0; i<=2*n-1; i++)
+            {
+                if( (double)(x[i])>=(double)(x[i+1]) )
+                {
+                    info = -4;
+                }
+            }
+            if( info<0 )
+            {
+                return;
+            }
+            wgauss = new double[2*n+1];
+            for(i=0; i<=2*n; i++)
+            {
+                wgauss[i] = 0;
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                wgauss[2*i+1] = wgtmp[i];
+            }
+        }
+
+
+        /*************************************************************************
+        Returns   Gauss   and   Gauss-Kronrod   nodes/weights  for  Gauss-Legendre
+        quadrature with N points.
+
+        GKQLegendreCalc (calculation) or  GKQLegendreTbl  (precomputed  table)  is
+        used depending on machine precision and number of nodes.
+
+        INPUT PARAMETERS:
+            N           -   number of Kronrod nodes, must be odd number, >=3.
+
+        OUTPUT PARAMETERS:
+            Info        -   error code:
+                            * -4    an  error   was   detected   when  calculating
+                                    weights/nodes.  N  is  too  large   to  obtain
+                                    weights/nodes  with  high   enough   accuracy.
+                                    Try  to   use   multiple   precision  version.
+                            * -3    internal eigenproblem solver hasn't converged
+                            * -1    incorrect N was passed
+                            * +1    OK
+            X           -   array[0..N-1] - array of quadrature nodes, ordered in
+                            ascending order.
+            WKronrod    -   array[0..N-1] - Kronrod weights
+            WGauss      -   array[0..N-1] - Gauss weights (interleaved with zeros
+                            corresponding to extended Kronrod nodes).
+
+
+          -- ALGLIB --
+             Copyright 12.05.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void gkqgenerategausslegendre(int n,
+            ref int info,
+            ref double[] x,
+            ref double[] wkronrod,
+            ref double[] wgauss)
+        {
+            double eps = 0;
+
+            info = 0;
+            x = new double[0];
+            wkronrod = new double[0];
+            wgauss = new double[0];
+
+            if( (double)(math.machineepsilon)>(double)(1.0E-32) && (((((n==15 || n==21) || n==31) || n==41) || n==51) || n==61) )
+            {
+                info = 1;
+                gkqlegendretbl(n, ref x, ref wkronrod, ref wgauss, ref eps);
+            }
+            else
+            {
+                gkqlegendrecalc(n, ref info, ref x, ref wkronrod, ref wgauss);
+            }
+        }
+
+
+        /*************************************************************************
+        Returns   Gauss   and   Gauss-Kronrod   nodes/weights   for   Gauss-Jacobi
+        quadrature on [-1,1] with weight function
+
+            W(x)=Power(1-x,Alpha)*Power(1+x,Beta).
+
+        INPUT PARAMETERS:
+            N           -   number of Kronrod nodes, must be odd number, >=3.
+            Alpha       -   power-law coefficient, Alpha>-1
+            Beta        -   power-law coefficient, Beta>-1
+
+        OUTPUT PARAMETERS:
+            Info        -   error code:
+                            * -5    no real and positive Gauss-Kronrod formula can
+                                    be created for such a weight function  with  a
+                                    given number of nodes.
+                            * -4    an  error  was   detected   when   calculating
+                                    weights/nodes. Alpha or  Beta  are  too  close
+                                    to -1 to obtain weights/nodes with high enough
+                                    accuracy, or, may be, N is too large.  Try  to
+                                    use multiple precision version.
+                            * -3    internal eigenproblem solver hasn't converged
+                            * -1    incorrect N was passed
+                            * +1    OK
+                            * +2    OK, but quadrature rule have exterior  nodes,
+                                    x[0]<-1 or x[n-1]>+1
+            X           -   array[0..N-1] - array of quadrature nodes, ordered in
+                            ascending order.
+            WKronrod    -   array[0..N-1] - Kronrod weights
+            WGauss      -   array[0..N-1] - Gauss weights (interleaved with zeros
+                            corresponding to extended Kronrod nodes).
+
+
+          -- ALGLIB --
+             Copyright 12.05.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void gkqgenerategaussjacobi(int n,
+            double alpha,
+            double beta,
+            ref int info,
+            ref double[] x,
+            ref double[] wkronrod,
+            ref double[] wgauss)
+        {
+            int clen = 0;
+            double[] a = new double[0];
+            double[] b = new double[0];
+            double alpha2 = 0;
+            double beta2 = 0;
+            double apb = 0;
+            double t = 0;
+            int i = 0;
+            double s = 0;
+
+            info = 0;
+            x = new double[0];
+            wkronrod = new double[0];
+            wgauss = new double[0];
+
+            if( n%2!=1 || n<3 )
+            {
+                info = -1;
+                return;
+            }
+            if( (double)(alpha)<=(double)(-1) || (double)(beta)<=(double)(-1) )
+            {
+                info = -1;
+                return;
+            }
+            clen = (int)Math.Ceiling((double)(3*(n/2))/(double)2)+1;
+            a = new double[clen];
+            b = new double[clen];
+            for(i=0; i<=clen-1; i++)
+            {
+                a[i] = 0;
+            }
+            apb = alpha+beta;
+            a[0] = (beta-alpha)/(apb+2);
+            t = (apb+1)*Math.Log(2)+gammafunc.lngamma(alpha+1, ref s)+gammafunc.lngamma(beta+1, ref s)-gammafunc.lngamma(apb+2, ref s);
+            if( (double)(t)>(double)(Math.Log(math.maxrealnumber)) )
+            {
+                info = -4;
+                return;
+            }
+            b[0] = Math.Exp(t);
+            if( clen>1 )
+            {
+                alpha2 = math.sqr(alpha);
+                beta2 = math.sqr(beta);
+                a[1] = (beta2-alpha2)/((apb+2)*(apb+4));
+                b[1] = 4*(alpha+1)*(beta+1)/((apb+3)*math.sqr(apb+2));
+                for(i=2; i<=clen-1; i++)
+                {
+                    a[i] = 0.25*(beta2-alpha2)/(i*i*(1+0.5*apb/i)*(1+0.5*(apb+2)/i));
+                    b[i] = 0.25*(1+alpha/i)*(1+beta/i)*(1+apb/i)/((1+0.5*(apb+1)/i)*(1+0.5*(apb-1)/i)*math.sqr(1+0.5*apb/i));
+                }
+            }
+            gkqgeneraterec(a, b, b[0], n, ref info, ref x, ref wkronrod, ref wgauss);
+            
+            //
+            // test basic properties to detect errors
+            //
+            if( info>0 )
+            {
+                if( (double)(x[0])<(double)(-1) || (double)(x[n-1])>(double)(1) )
+                {
+                    info = 2;
+                }
+                for(i=0; i<=n-2; i++)
+                {
+                    if( (double)(x[i])>=(double)(x[i+1]) )
+                    {
+                        info = -4;
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Returns Gauss and Gauss-Kronrod nodes for quadrature with N points.
+
+        Reduction to tridiagonal eigenproblem is used.
+
+        INPUT PARAMETERS:
+            N           -   number of Kronrod nodes, must be odd number, >=3.
+
+        OUTPUT PARAMETERS:
+            Info        -   error code:
+                            * -4    an  error   was   detected   when  calculating
+                                    weights/nodes.  N  is  too  large   to  obtain
+                                    weights/nodes  with  high   enough   accuracy.
+                                    Try  to   use   multiple   precision  version.
+                            * -3    internal eigenproblem solver hasn't converged
+                            * -1    incorrect N was passed
+                            * +1    OK
+            X           -   array[0..N-1] - array of quadrature nodes, ordered in
+                            ascending order.
+            WKronrod    -   array[0..N-1] - Kronrod weights
+            WGauss      -   array[0..N-1] - Gauss weights (interleaved with zeros
+                            corresponding to extended Kronrod nodes).
+
+          -- ALGLIB --
+             Copyright 12.05.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void gkqlegendrecalc(int n,
+            ref int info,
+            ref double[] x,
+            ref double[] wkronrod,
+            ref double[] wgauss)
+        {
+            double[] alpha = new double[0];
+            double[] beta = new double[0];
+            int alen = 0;
+            int blen = 0;
+            double mu0 = 0;
+            int k = 0;
+            int i = 0;
+
+            info = 0;
+            x = new double[0];
+            wkronrod = new double[0];
+            wgauss = new double[0];
+
+            if( n%2!=1 || n<3 )
+            {
+                info = -1;
+                return;
+            }
+            mu0 = 2;
+            alen = (int)Math.Floor((double)(3*(n/2))/(double)2)+1;
+            blen = (int)Math.Ceiling((double)(3*(n/2))/(double)2)+1;
+            alpha = new double[alen];
+            beta = new double[blen];
+            for(k=0; k<=alen-1; k++)
+            {
+                alpha[k] = 0;
+            }
+            beta[0] = 2;
+            for(k=1; k<=blen-1; k++)
+            {
+                beta[k] = 1/(4-1/math.sqr(k));
+            }
+            gkqgeneraterec(alpha, beta, mu0, n, ref info, ref x, ref wkronrod, ref wgauss);
+            
+            //
+            // test basic properties to detect errors
+            //
+            if( info>0 )
+            {
+                if( (double)(x[0])<(double)(-1) || (double)(x[n-1])>(double)(1) )
+                {
+                    info = -4;
+                }
+                for(i=0; i<=n-2; i++)
+                {
+                    if( (double)(x[i])>=(double)(x[i+1]) )
+                    {
+                        info = -4;
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Returns Gauss and Gauss-Kronrod nodes for quadrature with N  points  using
+        pre-calculated table. Nodes/weights were  computed  with  accuracy  up  to
+        1.0E-32 (if MPFR version of ALGLIB is used). In standard double  precision
+        accuracy reduces to something about 2.0E-16 (depending  on your compiler's
+        handling of long floating point constants).
+
+        INPUT PARAMETERS:
+            N           -   number of Kronrod nodes.
+                            N can be 15, 21, 31, 41, 51, 61.
+
+        OUTPUT PARAMETERS:
+            X           -   array[0..N-1] - array of quadrature nodes, ordered in
+                            ascending order.
+            WKronrod    -   array[0..N-1] - Kronrod weights
+            WGauss      -   array[0..N-1] - Gauss weights (interleaved with zeros
+                            corresponding to extended Kronrod nodes).
+
+
+          -- ALGLIB --
+             Copyright 12.05.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void gkqlegendretbl(int n,
+            ref double[] x,
+            ref double[] wkronrod,
+            ref double[] wgauss,
+            ref double eps)
+        {
+            int i = 0;
+            int ng = 0;
+            int[] p1 = new int[0];
+            int[] p2 = new int[0];
+            double tmp = 0;
+
+            x = new double[0];
+            wkronrod = new double[0];
+            wgauss = new double[0];
+            eps = 0;
+
+            
+            //
+            // these initializers are not really necessary,
+            // but without them compiler complains about uninitialized locals
+            //
+            ng = 0;
+            
+            //
+            // Process
+            //
+            alglib.ap.assert(((((n==15 || n==21) || n==31) || n==41) || n==51) || n==61, "GKQNodesTbl: incorrect N!");
+            x = new double[n];
+            wkronrod = new double[n];
+            wgauss = new double[n];
+            for(i=0; i<=n-1; i++)
+            {
+                x[i] = 0;
+                wkronrod[i] = 0;
+                wgauss[i] = 0;
+            }
+            eps = Math.Max(math.machineepsilon, 1.0E-32);
+            if( n==15 )
+            {
+                ng = 4;
+                wgauss[0] = 0.129484966168869693270611432679082;
+                wgauss[1] = 0.279705391489276667901467771423780;
+                wgauss[2] = 0.381830050505118944950369775488975;
+                wgauss[3] = 0.417959183673469387755102040816327;
+                x[0] = 0.991455371120812639206854697526329;
+                x[1] = 0.949107912342758524526189684047851;
+                x[2] = 0.864864423359769072789712788640926;
+                x[3] = 0.741531185599394439863864773280788;
+                x[4] = 0.586087235467691130294144838258730;
+                x[5] = 0.405845151377397166906606412076961;
+                x[6] = 0.207784955007898467600689403773245;
+                x[7] = 0.000000000000000000000000000000000;
+                wkronrod[0] = 0.022935322010529224963732008058970;
+                wkronrod[1] = 0.063092092629978553290700663189204;
+                wkronrod[2] = 0.104790010322250183839876322541518;
+                wkronrod[3] = 0.140653259715525918745189590510238;
+                wkronrod[4] = 0.169004726639267902826583426598550;
+                wkronrod[5] = 0.190350578064785409913256402421014;
+                wkronrod[6] = 0.204432940075298892414161999234649;
+                wkronrod[7] = 0.209482141084727828012999174891714;
+            }
+            if( n==21 )
+            {
+                ng = 5;
+                wgauss[0] = 0.066671344308688137593568809893332;
+                wgauss[1] = 0.149451349150580593145776339657697;
+                wgauss[2] = 0.219086362515982043995534934228163;
+                wgauss[3] = 0.269266719309996355091226921569469;
+                wgauss[4] = 0.295524224714752870173892994651338;
+                x[0] = 0.995657163025808080735527280689003;
+                x[1] = 0.973906528517171720077964012084452;
+                x[2] = 0.930157491355708226001207180059508;
+                x[3] = 0.865063366688984510732096688423493;
+                x[4] = 0.780817726586416897063717578345042;
+                x[5] = 0.679409568299024406234327365114874;
+                x[6] = 0.562757134668604683339000099272694;
+                x[7] = 0.433395394129247190799265943165784;
+                x[8] = 0.294392862701460198131126603103866;
+                x[9] = 0.148874338981631210884826001129720;
+                x[10] = 0.000000000000000000000000000000000;
+                wkronrod[0] = 0.011694638867371874278064396062192;
+                wkronrod[1] = 0.032558162307964727478818972459390;
+                wkronrod[2] = 0.054755896574351996031381300244580;
+                wkronrod[3] = 0.075039674810919952767043140916190;
+                wkronrod[4] = 0.093125454583697605535065465083366;
+                wkronrod[5] = 0.109387158802297641899210590325805;
+                wkronrod[6] = 0.123491976262065851077958109831074;
+                wkronrod[7] = 0.134709217311473325928054001771707;
+                wkronrod[8] = 0.142775938577060080797094273138717;
+                wkronrod[9] = 0.147739104901338491374841515972068;
+                wkronrod[10] = 0.149445554002916905664936468389821;
+            }
+            if( n==31 )
+            {
+                ng = 8;
+                wgauss[0] = 0.030753241996117268354628393577204;
+                wgauss[1] = 0.070366047488108124709267416450667;
+                wgauss[2] = 0.107159220467171935011869546685869;
+                wgauss[3] = 0.139570677926154314447804794511028;
+                wgauss[4] = 0.166269205816993933553200860481209;
+                wgauss[5] = 0.186161000015562211026800561866423;
+                wgauss[6] = 0.198431485327111576456118326443839;
+                wgauss[7] = 0.202578241925561272880620199967519;
+                x[0] = 0.998002298693397060285172840152271;
+                x[1] = 0.987992518020485428489565718586613;
+                x[2] = 0.967739075679139134257347978784337;
+                x[3] = 0.937273392400705904307758947710209;
+                x[4] = 0.897264532344081900882509656454496;
+                x[5] = 0.848206583410427216200648320774217;
+                x[6] = 0.790418501442465932967649294817947;
+                x[7] = 0.724417731360170047416186054613938;
+                x[8] = 0.650996741297416970533735895313275;
+                x[9] = 0.570972172608538847537226737253911;
+                x[10] = 0.485081863640239680693655740232351;
+                x[11] = 0.394151347077563369897207370981045;
+                x[12] = 0.299180007153168812166780024266389;
+                x[13] = 0.201194093997434522300628303394596;
+                x[14] = 0.101142066918717499027074231447392;
+                x[15] = 0.000000000000000000000000000000000;
+                wkronrod[0] = 0.005377479872923348987792051430128;
+                wkronrod[1] = 0.015007947329316122538374763075807;
+                wkronrod[2] = 0.025460847326715320186874001019653;
+                wkronrod[3] = 0.035346360791375846222037948478360;
+                wkronrod[4] = 0.044589751324764876608227299373280;
+                wkronrod[5] = 0.053481524690928087265343147239430;
+                wkronrod[6] = 0.062009567800670640285139230960803;
+                wkronrod[7] = 0.069854121318728258709520077099147;
+                wkronrod[8] = 0.076849680757720378894432777482659;
+                wkronrod[9] = 0.083080502823133021038289247286104;
+                wkronrod[10] = 0.088564443056211770647275443693774;
+                wkronrod[11] = 0.093126598170825321225486872747346;
+                wkronrod[12] = 0.096642726983623678505179907627589;
+                wkronrod[13] = 0.099173598721791959332393173484603;
+                wkronrod[14] = 0.100769845523875595044946662617570;
+                wkronrod[15] = 0.101330007014791549017374792767493;
+            }
+            if( n==41 )
+            {
+                ng = 10;
+                wgauss[0] = 0.017614007139152118311861962351853;
+                wgauss[1] = 0.040601429800386941331039952274932;
+                wgauss[2] = 0.062672048334109063569506535187042;
+                wgauss[3] = 0.083276741576704748724758143222046;
+                wgauss[4] = 0.101930119817240435036750135480350;
+                wgauss[5] = 0.118194531961518417312377377711382;
+                wgauss[6] = 0.131688638449176626898494499748163;
+                wgauss[7] = 0.142096109318382051329298325067165;
+                wgauss[8] = 0.149172986472603746787828737001969;
+                wgauss[9] = 0.152753387130725850698084331955098;
+                x[0] = 0.998859031588277663838315576545863;
+                x[1] = 0.993128599185094924786122388471320;
+                x[2] = 0.981507877450250259193342994720217;
+                x[3] = 0.963971927277913791267666131197277;
+                x[4] = 0.940822633831754753519982722212443;
+                x[5] = 0.912234428251325905867752441203298;
+                x[6] = 0.878276811252281976077442995113078;
+                x[7] = 0.839116971822218823394529061701521;
+                x[8] = 0.795041428837551198350638833272788;
+                x[9] = 0.746331906460150792614305070355642;
+                x[10] = 0.693237656334751384805490711845932;
+                x[11] = 0.636053680726515025452836696226286;
+                x[12] = 0.575140446819710315342946036586425;
+                x[13] = 0.510867001950827098004364050955251;
+                x[14] = 0.443593175238725103199992213492640;
+                x[15] = 0.373706088715419560672548177024927;
+                x[16] = 0.301627868114913004320555356858592;
+                x[17] = 0.227785851141645078080496195368575;
+                x[18] = 0.152605465240922675505220241022678;
+                x[19] = 0.076526521133497333754640409398838;
+                x[20] = 0.000000000000000000000000000000000;
+                wkronrod[0] = 0.003073583718520531501218293246031;
+                wkronrod[1] = 0.008600269855642942198661787950102;
+                wkronrod[2] = 0.014626169256971252983787960308868;
+                wkronrod[3] = 0.020388373461266523598010231432755;
+                wkronrod[4] = 0.025882133604951158834505067096153;
+                wkronrod[5] = 0.031287306777032798958543119323801;
+                wkronrod[6] = 0.036600169758200798030557240707211;
+                wkronrod[7] = 0.041668873327973686263788305936895;
+                wkronrod[8] = 0.046434821867497674720231880926108;
+                wkronrod[9] = 0.050944573923728691932707670050345;
+                wkronrod[10] = 0.055195105348285994744832372419777;
+                wkronrod[11] = 0.059111400880639572374967220648594;
+                wkronrod[12] = 0.062653237554781168025870122174255;
+                wkronrod[13] = 0.065834597133618422111563556969398;
+                wkronrod[14] = 0.068648672928521619345623411885368;
+                wkronrod[15] = 0.071054423553444068305790361723210;
+                wkronrod[16] = 0.073030690332786667495189417658913;
+                wkronrod[17] = 0.074582875400499188986581418362488;
+                wkronrod[18] = 0.075704497684556674659542775376617;
+                wkronrod[19] = 0.076377867672080736705502835038061;
+                wkronrod[20] = 0.076600711917999656445049901530102;
+            }
+            if( n==51 )
+            {
+                ng = 13;
+                wgauss[0] = 0.011393798501026287947902964113235;
+                wgauss[1] = 0.026354986615032137261901815295299;
+                wgauss[2] = 0.040939156701306312655623487711646;
+                wgauss[3] = 0.054904695975835191925936891540473;
+                wgauss[4] = 0.068038333812356917207187185656708;
+                wgauss[5] = 0.080140700335001018013234959669111;
+                wgauss[6] = 0.091028261982963649811497220702892;
+                wgauss[7] = 0.100535949067050644202206890392686;
+                wgauss[8] = 0.108519624474263653116093957050117;
+                wgauss[9] = 0.114858259145711648339325545869556;
+                wgauss[10] = 0.119455763535784772228178126512901;
+                wgauss[11] = 0.122242442990310041688959518945852;
+                wgauss[12] = 0.123176053726715451203902873079050;
+                x[0] = 0.999262104992609834193457486540341;
+                x[1] = 0.995556969790498097908784946893902;
+                x[2] = 0.988035794534077247637331014577406;
+                x[3] = 0.976663921459517511498315386479594;
+                x[4] = 0.961614986425842512418130033660167;
+                x[5] = 0.942974571228974339414011169658471;
+                x[6] = 0.920747115281701561746346084546331;
+                x[7] = 0.894991997878275368851042006782805;
+                x[8] = 0.865847065293275595448996969588340;
+                x[9] = 0.833442628760834001421021108693570;
+                x[10] = 0.797873797998500059410410904994307;
+                x[11] = 0.759259263037357630577282865204361;
+                x[12] = 0.717766406813084388186654079773298;
+                x[13] = 0.673566368473468364485120633247622;
+                x[14] = 0.626810099010317412788122681624518;
+                x[15] = 0.577662930241222967723689841612654;
+                x[16] = 0.526325284334719182599623778158010;
+                x[17] = 0.473002731445714960522182115009192;
+                x[18] = 0.417885382193037748851814394594572;
+                x[19] = 0.361172305809387837735821730127641;
+                x[20] = 0.303089538931107830167478909980339;
+                x[21] = 0.243866883720988432045190362797452;
+                x[22] = 0.183718939421048892015969888759528;
+                x[23] = 0.122864692610710396387359818808037;
+                x[24] = 0.061544483005685078886546392366797;
+                x[25] = 0.000000000000000000000000000000000;
+                wkronrod[0] = 0.001987383892330315926507851882843;
+                wkronrod[1] = 0.005561932135356713758040236901066;
+                wkronrod[2] = 0.009473973386174151607207710523655;
+                wkronrod[3] = 0.013236229195571674813656405846976;
+                wkronrod[4] = 0.016847817709128298231516667536336;
+                wkronrod[5] = 0.020435371145882835456568292235939;
+                wkronrod[6] = 0.024009945606953216220092489164881;
+                wkronrod[7] = 0.027475317587851737802948455517811;
+                wkronrod[8] = 0.030792300167387488891109020215229;
+                wkronrod[9] = 0.034002130274329337836748795229551;
+                wkronrod[10] = 0.037116271483415543560330625367620;
+                wkronrod[11] = 0.040083825504032382074839284467076;
+                wkronrod[12] = 0.042872845020170049476895792439495;
+                wkronrod[13] = 0.045502913049921788909870584752660;
+                wkronrod[14] = 0.047982537138836713906392255756915;
+                wkronrod[15] = 0.050277679080715671963325259433440;
+                wkronrod[16] = 0.052362885806407475864366712137873;
+                wkronrod[17] = 0.054251129888545490144543370459876;
+                wkronrod[18] = 0.055950811220412317308240686382747;
+                wkronrod[19] = 0.057437116361567832853582693939506;
+                wkronrod[20] = 0.058689680022394207961974175856788;
+                wkronrod[21] = 0.059720340324174059979099291932562;
+                wkronrod[22] = 0.060539455376045862945360267517565;
+                wkronrod[23] = 0.061128509717053048305859030416293;
+                wkronrod[24] = 0.061471189871425316661544131965264;
+                wkronrod[25] = 0.061580818067832935078759824240055;
+            }
+            if( n==61 )
+            {
+                ng = 15;
+                wgauss[0] = 0.007968192496166605615465883474674;
+                wgauss[1] = 0.018466468311090959142302131912047;
+                wgauss[2] = 0.028784707883323369349719179611292;
+                wgauss[3] = 0.038799192569627049596801936446348;
+                wgauss[4] = 0.048402672830594052902938140422808;
+                wgauss[5] = 0.057493156217619066481721689402056;
+                wgauss[6] = 0.065974229882180495128128515115962;
+                wgauss[7] = 0.073755974737705206268243850022191;
+                wgauss[8] = 0.080755895229420215354694938460530;
+                wgauss[9] = 0.086899787201082979802387530715126;
+                wgauss[10] = 0.092122522237786128717632707087619;
+                wgauss[11] = 0.096368737174644259639468626351810;
+                wgauss[12] = 0.099593420586795267062780282103569;
+                wgauss[13] = 0.101762389748405504596428952168554;
+                wgauss[14] = 0.102852652893558840341285636705415;
+                x[0] = 0.999484410050490637571325895705811;
+                x[1] = 0.996893484074649540271630050918695;
+                x[2] = 0.991630996870404594858628366109486;
+                x[3] = 0.983668123279747209970032581605663;
+                x[4] = 0.973116322501126268374693868423707;
+                x[5] = 0.960021864968307512216871025581798;
+                x[6] = 0.944374444748559979415831324037439;
+                x[7] = 0.926200047429274325879324277080474;
+                x[8] = 0.905573307699907798546522558925958;
+                x[9] = 0.882560535792052681543116462530226;
+                x[10] = 0.857205233546061098958658510658944;
+                x[11] = 0.829565762382768397442898119732502;
+                x[12] = 0.799727835821839083013668942322683;
+                x[13] = 0.767777432104826194917977340974503;
+                x[14] = 0.733790062453226804726171131369528;
+                x[15] = 0.697850494793315796932292388026640;
+                x[16] = 0.660061064126626961370053668149271;
+                x[17] = 0.620526182989242861140477556431189;
+                x[18] = 0.579345235826361691756024932172540;
+                x[19] = 0.536624148142019899264169793311073;
+                x[20] = 0.492480467861778574993693061207709;
+                x[21] = 0.447033769538089176780609900322854;
+                x[22] = 0.400401254830394392535476211542661;
+                x[23] = 0.352704725530878113471037207089374;
+                x[24] = 0.304073202273625077372677107199257;
+                x[25] = 0.254636926167889846439805129817805;
+                x[26] = 0.204525116682309891438957671002025;
+                x[27] = 0.153869913608583546963794672743256;
+                x[28] = 0.102806937966737030147096751318001;
+                x[29] = 0.051471842555317695833025213166723;
+                x[30] = 0.000000000000000000000000000000000;
+                wkronrod[0] = 0.001389013698677007624551591226760;
+                wkronrod[1] = 0.003890461127099884051267201844516;
+                wkronrod[2] = 0.006630703915931292173319826369750;
+                wkronrod[3] = 0.009273279659517763428441146892024;
+                wkronrod[4] = 0.011823015253496341742232898853251;
+                wkronrod[5] = 0.014369729507045804812451432443580;
+                wkronrod[6] = 0.016920889189053272627572289420322;
+                wkronrod[7] = 0.019414141193942381173408951050128;
+                wkronrod[8] = 0.021828035821609192297167485738339;
+                wkronrod[9] = 0.024191162078080601365686370725232;
+                wkronrod[10] = 0.026509954882333101610601709335075;
+                wkronrod[11] = 0.028754048765041292843978785354334;
+                wkronrod[12] = 0.030907257562387762472884252943092;
+                wkronrod[13] = 0.032981447057483726031814191016854;
+                wkronrod[14] = 0.034979338028060024137499670731468;
+                wkronrod[15] = 0.036882364651821229223911065617136;
+                wkronrod[16] = 0.038678945624727592950348651532281;
+                wkronrod[17] = 0.040374538951535959111995279752468;
+                wkronrod[18] = 0.041969810215164246147147541285970;
+                wkronrod[19] = 0.043452539701356069316831728117073;
+                wkronrod[20] = 0.044814800133162663192355551616723;
+                wkronrod[21] = 0.046059238271006988116271735559374;
+                wkronrod[22] = 0.047185546569299153945261478181099;
+                wkronrod[23] = 0.048185861757087129140779492298305;
+                wkronrod[24] = 0.049055434555029778887528165367238;
+                wkronrod[25] = 0.049795683427074206357811569379942;
+                wkronrod[26] = 0.050405921402782346840893085653585;
+                wkronrod[27] = 0.050881795898749606492297473049805;
+                wkronrod[28] = 0.051221547849258772170656282604944;
+                wkronrod[29] = 0.051426128537459025933862879215781;
+                wkronrod[30] = 0.051494729429451567558340433647099;
+            }
+            
+            //
+            // copy nodes
+            //
+            for(i=n-1; i>=n/2; i--)
+            {
+                x[i] = -x[n-1-i];
+            }
+            
+            //
+            // copy Kronrod weights
+            //
+            for(i=n-1; i>=n/2; i--)
+            {
+                wkronrod[i] = wkronrod[n-1-i];
+            }
+            
+            //
+            // copy Gauss weights
+            //
+            for(i=ng-1; i>=0; i--)
+            {
+                wgauss[n-2-2*i] = wgauss[i];
+                wgauss[1+2*i] = wgauss[i];
+            }
+            for(i=0; i<=n/2; i++)
+            {
+                wgauss[2*i] = 0;
+            }
+            
+            //
+            // reorder
+            //
+            tsort.tagsort(ref x, n, ref p1, ref p2);
+            for(i=0; i<=n-1; i++)
+            {
+                tmp = wkronrod[i];
+                wkronrod[i] = wkronrod[p2[i]];
+                wkronrod[p2[i]] = tmp;
+                tmp = wgauss[i];
+                wgauss[i] = wgauss[p2[i]];
+                wgauss[p2[i]] = tmp;
+            }
+        }
+
+
+    }
+    public class autogk
+    {
+        /*************************************************************************
+        Integration report:
+        * TerminationType = completetion code:
+            * -5    non-convergence of Gauss-Kronrod nodes
+                    calculation subroutine.
+            * -1    incorrect parameters were specified
+            *  1    OK
+        * Rep.NFEV countains number of function calculations
+        * Rep.NIntervals contains number of intervals [a,b]
+          was partitioned into.
+        *************************************************************************/
+        public class autogkreport
+        {
+            public int terminationtype;
+            public int nfev;
+            public int nintervals;
+        };
+
+
+        public class autogkinternalstate
+        {
+            public double a;
+            public double b;
+            public double eps;
+            public double xwidth;
+            public double x;
+            public double f;
+            public int info;
+            public double r;
+            public double[,] heap;
+            public int heapsize;
+            public int heapwidth;
+            public int heapused;
+            public double sumerr;
+            public double sumabs;
+            public double[] qn;
+            public double[] wg;
+            public double[] wk;
+            public double[] wr;
+            public int n;
+            public rcommstate rstate;
+            public autogkinternalstate()
+            {
+                heap = new double[0,0];
+                qn = new double[0];
+                wg = new double[0];
+                wk = new double[0];
+                wr = new double[0];
+                rstate = new rcommstate();
+            }
+        };
+
+
+        /*************************************************************************
+        This structure stores state of the integration algorithm.
+
+        Although this class has public fields,  they are not intended for external
+        use. You should use ALGLIB functions to work with this class:
+        * autogksmooth()/AutoGKSmoothW()/... to create objects
+        * autogkintegrate() to begin integration
+        * autogkresults() to get results
+        *************************************************************************/
+        public class autogkstate
+        {
+            public double a;
+            public double b;
+            public double alpha;
+            public double beta;
+            public double xwidth;
+            public double x;
+            public double xminusa;
+            public double bminusx;
+            public bool needf;
+            public double f;
+            public int wrappermode;
+            public autogkinternalstate internalstate;
+            public rcommstate rstate;
+            public double v;
+            public int terminationtype;
+            public int nfev;
+            public int nintervals;
+            public autogkstate()
+            {
+                internalstate = new autogkinternalstate();
+                rstate = new rcommstate();
+            }
+        };
+
+
+
+
+        public const int maxsubintervals = 10000;
+
+
+        /*************************************************************************
+        Integration of a smooth function F(x) on a finite interval [a,b].
+
+        Fast-convergent algorithm based on a Gauss-Kronrod formula is used. Result
+        is calculated with accuracy close to the machine precision.
+
+        Algorithm works well only with smooth integrands.  It  may  be  used  with
+        continuous non-smooth integrands, but with  less  performance.
+
+        It should never be used with integrands which have integrable singularities
+        at lower or upper limits - algorithm may crash. Use AutoGKSingular in such
+        cases.
+
+        INPUT PARAMETERS:
+            A, B    -   interval boundaries (A<B, A=B or A>B)
+            
+        OUTPUT PARAMETERS
+            State   -   structure which stores algorithm state
+
+        SEE ALSO
+            AutoGKSmoothW, AutoGKSingular, AutoGKResults.
+            
+
+          -- ALGLIB --
+             Copyright 06.05.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void autogksmooth(double a,
+            double b,
+            autogkstate state)
+        {
+            alglib.ap.assert(math.isfinite(a), "AutoGKSmooth: A is not finite!");
+            alglib.ap.assert(math.isfinite(b), "AutoGKSmooth: B is not finite!");
+            autogksmoothw(a, b, 0.0, state);
+        }
+
+
+        /*************************************************************************
+        Integration of a smooth function F(x) on a finite interval [a,b].
+
+        This subroutine is same as AutoGKSmooth(), but it guarantees that interval
+        [a,b] is partitioned into subintervals which have width at most XWidth.
+
+        Subroutine  can  be  used  when  integrating nearly-constant function with
+        narrow "bumps" (about XWidth wide). If "bumps" are too narrow, AutoGKSmooth
+        subroutine can overlook them.
+
+        INPUT PARAMETERS:
+            A, B    -   interval boundaries (A<B, A=B or A>B)
+
+        OUTPUT PARAMETERS
+            State   -   structure which stores algorithm state
+
+        SEE ALSO
+            AutoGKSmooth, AutoGKSingular, AutoGKResults.
+
+
+          -- ALGLIB --
+             Copyright 06.05.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void autogksmoothw(double a,
+            double b,
+            double xwidth,
+            autogkstate state)
+        {
+            alglib.ap.assert(math.isfinite(a), "AutoGKSmoothW: A is not finite!");
+            alglib.ap.assert(math.isfinite(b), "AutoGKSmoothW: B is not finite!");
+            alglib.ap.assert(math.isfinite(xwidth), "AutoGKSmoothW: XWidth is not finite!");
+            state.wrappermode = 0;
+            state.a = a;
+            state.b = b;
+            state.xwidth = xwidth;
+            state.needf = false;
+            state.rstate.ra = new double[10+1];
+            state.rstate.stage = -1;
+        }
+
+
+        /*************************************************************************
+        Integration on a finite interval [A,B].
+        Integrand have integrable singularities at A/B.
+
+        F(X) must diverge as "(x-A)^alpha" at A, as "(B-x)^beta" at B,  with known
+        alpha/beta (alpha>-1, beta>-1).  If alpha/beta  are  not known,  estimates
+        from below can be used (but these estimates should be greater than -1 too).
+
+        One  of  alpha/beta variables (or even both alpha/beta) may be equal to 0,
+        which means than function F(x) is non-singular at A/B. Anyway (singular at
+        bounds or not), function F(x) is supposed to be continuous on (A,B).
+
+        Fast-convergent algorithm based on a Gauss-Kronrod formula is used. Result
+        is calculated with accuracy close to the machine precision.
+
+        INPUT PARAMETERS:
+            A, B    -   interval boundaries (A<B, A=B or A>B)
+            Alpha   -   power-law coefficient of the F(x) at A,
+                        Alpha>-1
+            Beta    -   power-law coefficient of the F(x) at B,
+                        Beta>-1
+
+        OUTPUT PARAMETERS
+            State   -   structure which stores algorithm state
+
+        SEE ALSO
+            AutoGKSmooth, AutoGKSmoothW, AutoGKResults.
+
+
+          -- ALGLIB --
+             Copyright 06.05.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void autogksingular(double a,
+            double b,
+            double alpha,
+            double beta,
+            autogkstate state)
+        {
+            alglib.ap.assert(math.isfinite(a), "AutoGKSingular: A is not finite!");
+            alglib.ap.assert(math.isfinite(b), "AutoGKSingular: B is not finite!");
+            alglib.ap.assert(math.isfinite(alpha), "AutoGKSingular: Alpha is not finite!");
+            alglib.ap.assert(math.isfinite(beta), "AutoGKSingular: Beta is not finite!");
+            state.wrappermode = 1;
+            state.a = a;
+            state.b = b;
+            state.alpha = alpha;
+            state.beta = beta;
+            state.xwidth = 0.0;
+            state.needf = false;
+            state.rstate.ra = new double[10+1];
+            state.rstate.stage = -1;
+        }
+
+
+        /*************************************************************************
+
+          -- ALGLIB --
+             Copyright 07.05.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static bool autogkiteration(autogkstate state)
+        {
+            bool result = new bool();
+            double s = 0;
+            double tmp = 0;
+            double eps = 0;
+            double a = 0;
+            double b = 0;
+            double x = 0;
+            double t = 0;
+            double alpha = 0;
+            double beta = 0;
+            double v1 = 0;
+            double v2 = 0;
+
+            
+            //
+            // Reverse communication preparations
+            // I know it looks ugly, but it works the same way
+            // anywhere from C++ to Python.
+            //
+            // This code initializes locals by:
+            // * random values determined during code
+            //   generation - on first subroutine call
+            // * values from previous call - on subsequent calls
+            //
+            if( state.rstate.stage>=0 )
+            {
+                s = state.rstate.ra[0];
+                tmp = state.rstate.ra[1];
+                eps = state.rstate.ra[2];
+                a = state.rstate.ra[3];
+                b = state.rstate.ra[4];
+                x = state.rstate.ra[5];
+                t = state.rstate.ra[6];
+                alpha = state.rstate.ra[7];
+                beta = state.rstate.ra[8];
+                v1 = state.rstate.ra[9];
+                v2 = state.rstate.ra[10];
+            }
+            else
+            {
+                s = -983;
+                tmp = -989;
+                eps = -834;
+                a = 900;
+                b = -287;
+                x = 364;
+                t = 214;
+                alpha = -338;
+                beta = -686;
+                v1 = 912;
+                v2 = 585;
+            }
+            if( state.rstate.stage==0 )
+            {
+                goto lbl_0;
+            }
+            if( state.rstate.stage==1 )
+            {
+                goto lbl_1;
+            }
+            if( state.rstate.stage==2 )
+            {
+                goto lbl_2;
+            }
+            
+            //
+            // Routine body
+            //
+            eps = 0;
+            a = state.a;
+            b = state.b;
+            alpha = state.alpha;
+            beta = state.beta;
+            state.terminationtype = -1;
+            state.nfev = 0;
+            state.nintervals = 0;
+            
+            //
+            // smooth function  at a finite interval
+            //
+            if( state.wrappermode!=0 )
+            {
+                goto lbl_3;
+            }
+            
+            //
+            // special case
+            //
+            if( (double)(a)==(double)(b) )
+            {
+                state.terminationtype = 1;
+                state.v = 0;
+                result = false;
+                return result;
+            }
+            
+            //
+            // general case
+            //
+            autogkinternalprepare(a, b, eps, state.xwidth, state.internalstate);
+        lbl_5:
+            if( !autogkinternaliteration(state.internalstate) )
+            {
+                goto lbl_6;
+            }
+            x = state.internalstate.x;
+            state.x = x;
+            state.xminusa = x-a;
+            state.bminusx = b-x;
+            state.needf = true;
+            state.rstate.stage = 0;
+            goto lbl_rcomm;
+        lbl_0:
+            state.needf = false;
+            state.nfev = state.nfev+1;
+            state.internalstate.f = state.f;
+            goto lbl_5;
+        lbl_6:
+            state.v = state.internalstate.r;
+            state.terminationtype = state.internalstate.info;
+            state.nintervals = state.internalstate.heapused;
+            result = false;
+            return result;
+        lbl_3:
+            
+            //
+            // function with power-law singularities at the ends of a finite interval
+            //
+            if( state.wrappermode!=1 )
+            {
+                goto lbl_7;
+            }
+            
+            //
+            // test coefficients
+            //
+            if( (double)(alpha)<=(double)(-1) || (double)(beta)<=(double)(-1) )
+            {
+                state.terminationtype = -1;
+                state.v = 0;
+                result = false;
+                return result;
+            }
+            
+            //
+            // special cases
+            //
+            if( (double)(a)==(double)(b) )
+            {
+                state.terminationtype = 1;
+                state.v = 0;
+                result = false;
+                return result;
+            }
+            
+            //
+            // reduction to general form
+            //
+            if( (double)(a)<(double)(b) )
+            {
+                s = 1;
+            }
+            else
+            {
+                s = -1;
+                tmp = a;
+                a = b;
+                b = tmp;
+                tmp = alpha;
+                alpha = beta;
+                beta = tmp;
+            }
+            alpha = Math.Min(alpha, 0);
+            beta = Math.Min(beta, 0);
+            
+            //
+            // first, integrate left half of [a,b]:
+            //     integral(f(x)dx, a, (b+a)/2) =
+            //     = 1/(1+alpha) * integral(t^(-alpha/(1+alpha))*f(a+t^(1/(1+alpha)))dt, 0, (0.5*(b-a))^(1+alpha))
+            //
+            autogkinternalprepare(0, Math.Pow(0.5*(b-a), 1+alpha), eps, state.xwidth, state.internalstate);
+        lbl_9:
+            if( !autogkinternaliteration(state.internalstate) )
+            {
+                goto lbl_10;
+            }
+            
+            //
+            // Fill State.X, State.XMinusA, State.BMinusX.
+            // Latter two are filled correctly even if B<A.
+            //
+            x = state.internalstate.x;
+            t = Math.Pow(x, 1/(1+alpha));
+            state.x = a+t;
+            if( (double)(s)>(double)(0) )
+            {
+                state.xminusa = t;
+                state.bminusx = b-(a+t);
+            }
+            else
+            {
+                state.xminusa = a+t-b;
+                state.bminusx = -t;
+            }
+            state.needf = true;
+            state.rstate.stage = 1;
+            goto lbl_rcomm;
+        lbl_1:
+            state.needf = false;
+            if( (double)(alpha)!=(double)(0) )
+            {
+                state.internalstate.f = state.f*Math.Pow(x, -(alpha/(1+alpha)))/(1+alpha);
+            }
+            else
+            {
+                state.internalstate.f = state.f;
+            }
+            state.nfev = state.nfev+1;
+            goto lbl_9;
+        lbl_10:
+            v1 = state.internalstate.r;
+            state.nintervals = state.nintervals+state.internalstate.heapused;
+            
+            //
+            // then, integrate right half of [a,b]:
+            //     integral(f(x)dx, (b+a)/2, b) =
+            //     = 1/(1+beta) * integral(t^(-beta/(1+beta))*f(b-t^(1/(1+beta)))dt, 0, (0.5*(b-a))^(1+beta))
+            //
+            autogkinternalprepare(0, Math.Pow(0.5*(b-a), 1+beta), eps, state.xwidth, state.internalstate);
+        lbl_11:
+            if( !autogkinternaliteration(state.internalstate) )
+            {
+                goto lbl_12;
+            }
+            
+            //
+            // Fill State.X, State.XMinusA, State.BMinusX.
+            // Latter two are filled correctly (X-A, B-X) even if B<A.
+            //
+            x = state.internalstate.x;
+            t = Math.Pow(x, 1/(1+beta));
+            state.x = b-t;
+            if( (double)(s)>(double)(0) )
+            {
+                state.xminusa = b-t-a;
+                state.bminusx = t;
+            }
+            else
+            {
+                state.xminusa = -t;
+                state.bminusx = a-(b-t);
+            }
+            state.needf = true;
+            state.rstate.stage = 2;
+            goto lbl_rcomm;
+        lbl_2:
+            state.needf = false;
+            if( (double)(beta)!=(double)(0) )
+            {
+                state.internalstate.f = state.f*Math.Pow(x, -(beta/(1+beta)))/(1+beta);
+            }
+            else
+            {
+                state.internalstate.f = state.f;
+            }
+            state.nfev = state.nfev+1;
+            goto lbl_11;
+        lbl_12:
+            v2 = state.internalstate.r;
+            state.nintervals = state.nintervals+state.internalstate.heapused;
+            
+            //
+            // final result
+            //
+            state.v = s*(v1+v2);
+            state.terminationtype = 1;
+            result = false;
+            return result;
+        lbl_7:
+            result = false;
+            return result;
+            
+            //
+            // Saving state
+            //
+        lbl_rcomm:
+            result = true;
+            state.rstate.ra[0] = s;
+            state.rstate.ra[1] = tmp;
+            state.rstate.ra[2] = eps;
+            state.rstate.ra[3] = a;
+            state.rstate.ra[4] = b;
+            state.rstate.ra[5] = x;
+            state.rstate.ra[6] = t;
+            state.rstate.ra[7] = alpha;
+            state.rstate.ra[8] = beta;
+            state.rstate.ra[9] = v1;
+            state.rstate.ra[10] = v2;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Adaptive integration results
+
+        Called after AutoGKIteration returned False.
+
+        Input parameters:
+            State   -   algorithm state (used by AutoGKIteration).
+
+        Output parameters:
+            V       -   integral(f(x)dx,a,b)
+            Rep     -   optimization report (see AutoGKReport description)
+
+          -- ALGLIB --
+             Copyright 14.11.2007 by Bochkanov Sergey
+        *************************************************************************/
+        public static void autogkresults(autogkstate state,
+            ref double v,
+            autogkreport rep)
+        {
+            v = 0;
+
+            v = state.v;
+            rep.terminationtype = state.terminationtype;
+            rep.nfev = state.nfev;
+            rep.nintervals = state.nintervals;
+        }
+
+
+        /*************************************************************************
+        Internal AutoGK subroutine
+        eps<0   - error
+        eps=0   - automatic eps selection
+
+        width<0 -   error
+        width=0 -   no width requirements
+        *************************************************************************/
+        private static void autogkinternalprepare(double a,
+            double b,
+            double eps,
+            double xwidth,
+            autogkinternalstate state)
+        {
+            
+            //
+            // Save settings
+            //
+            state.a = a;
+            state.b = b;
+            state.eps = eps;
+            state.xwidth = xwidth;
+            
+            //
+            // Prepare RComm structure
+            //
+            state.rstate.ia = new int[3+1];
+            state.rstate.ra = new double[8+1];
+            state.rstate.stage = -1;
+        }
+
+
+        /*************************************************************************
+        Internal AutoGK subroutine
+        *************************************************************************/
+        private static bool autogkinternaliteration(autogkinternalstate state)
+        {
+            bool result = new bool();
+            double c1 = 0;
+            double c2 = 0;
+            int i = 0;
+            int j = 0;
+            double intg = 0;
+            double intk = 0;
+            double inta = 0;
+            double v = 0;
+            double ta = 0;
+            double tb = 0;
+            int ns = 0;
+            double qeps = 0;
+            int info = 0;
+
+            
+            //
+            // Reverse communication preparations
+            // I know it looks ugly, but it works the same way
+            // anywhere from C++ to Python.
+            //
+            // This code initializes locals by:
+            // * random values determined during code
+            //   generation - on first subroutine call
+            // * values from previous call - on subsequent calls
+            //
+            if( state.rstate.stage>=0 )
+            {
+                i = state.rstate.ia[0];
+                j = state.rstate.ia[1];
+                ns = state.rstate.ia[2];
+                info = state.rstate.ia[3];
+                c1 = state.rstate.ra[0];
+                c2 = state.rstate.ra[1];
+                intg = state.rstate.ra[2];
+                intk = state.rstate.ra[3];
+                inta = state.rstate.ra[4];
+                v = state.rstate.ra[5];
+                ta = state.rstate.ra[6];
+                tb = state.rstate.ra[7];
+                qeps = state.rstate.ra[8];
+            }
+            else
+            {
+                i = 497;
+                j = -271;
+                ns = -581;
+                info = 745;
+                c1 = -533;
+                c2 = -77;
+                intg = 678;
+                intk = -293;
+                inta = 316;
+                v = 647;
+                ta = -756;
+                tb = 830;
+                qeps = -871;
+            }
+            if( state.rstate.stage==0 )
+            {
+                goto lbl_0;
+            }
+            if( state.rstate.stage==1 )
+            {
+                goto lbl_1;
+            }
+            if( state.rstate.stage==2 )
+            {
+                goto lbl_2;
+            }
+            
+            //
+            // Routine body
+            //
+            
+            //
+            // initialize quadratures.
+            // use 15-point Gauss-Kronrod formula.
+            //
+            state.n = 15;
+            gkq.gkqgenerategausslegendre(state.n, ref info, ref state.qn, ref state.wk, ref state.wg);
+            if( info<0 )
+            {
+                state.info = -5;
+                state.r = 0;
+                result = false;
+                return result;
+            }
+            state.wr = new double[state.n];
+            for(i=0; i<=state.n-1; i++)
+            {
+                if( i==0 )
+                {
+                    state.wr[i] = 0.5*Math.Abs(state.qn[1]-state.qn[0]);
+                    continue;
+                }
+                if( i==state.n-1 )
+                {
+                    state.wr[state.n-1] = 0.5*Math.Abs(state.qn[state.n-1]-state.qn[state.n-2]);
+                    continue;
+                }
+                state.wr[i] = 0.5*Math.Abs(state.qn[i-1]-state.qn[i+1]);
+            }
+            
+            //
+            // special case
+            //
+            if( (double)(state.a)==(double)(state.b) )
+            {
+                state.info = 1;
+                state.r = 0;
+                result = false;
+                return result;
+            }
+            
+            //
+            // test parameters
+            //
+            if( (double)(state.eps)<(double)(0) || (double)(state.xwidth)<(double)(0) )
+            {
+                state.info = -1;
+                state.r = 0;
+                result = false;
+                return result;
+            }
+            state.info = 1;
+            if( (double)(state.eps)==(double)(0) )
+            {
+                state.eps = 100000*math.machineepsilon;
+            }
+            
+            //
+            // First, prepare heap
+            // * column 0   -   absolute error
+            // * column 1   -   integral of a F(x) (calculated using Kronrod extension nodes)
+            // * column 2   -   integral of a |F(x)| (calculated using modified rect. method)
+            // * column 3   -   left boundary of a subinterval
+            // * column 4   -   right boundary of a subinterval
+            //
+            if( (double)(state.xwidth)!=(double)(0) )
+            {
+                goto lbl_3;
+            }
+            
+            //
+            // no maximum width requirements
+            // start from one big subinterval
+            //
+            state.heapwidth = 5;
+            state.heapsize = 1;
+            state.heapused = 1;
+            state.heap = new double[state.heapsize, state.heapwidth];
+            c1 = 0.5*(state.b-state.a);
+            c2 = 0.5*(state.b+state.a);
+            intg = 0;
+            intk = 0;
+            inta = 0;
+            i = 0;
+        lbl_5:
+            if( i>state.n-1 )
+            {
+                goto lbl_7;
+            }
+            
+            //
+            // obtain F
+            //
+            state.x = c1*state.qn[i]+c2;
+            state.rstate.stage = 0;
+            goto lbl_rcomm;
+        lbl_0:
+            v = state.f;
+            
+            //
+            // Gauss-Kronrod formula
+            //
+            intk = intk+v*state.wk[i];
+            if( i%2==1 )
+            {
+                intg = intg+v*state.wg[i];
+            }
+            
+            //
+            // Integral |F(x)|
+            // Use rectangles method
+            //
+            inta = inta+Math.Abs(v)*state.wr[i];
+            i = i+1;
+            goto lbl_5;
+        lbl_7:
+            intk = intk*(state.b-state.a)*0.5;
+            intg = intg*(state.b-state.a)*0.5;
+            inta = inta*(state.b-state.a)*0.5;
+            state.heap[0,0] = Math.Abs(intg-intk);
+            state.heap[0,1] = intk;
+            state.heap[0,2] = inta;
+            state.heap[0,3] = state.a;
+            state.heap[0,4] = state.b;
+            state.sumerr = state.heap[0,0];
+            state.sumabs = Math.Abs(inta);
+            goto lbl_4;
+        lbl_3:
+            
+            //
+            // maximum subinterval should be no more than XWidth.
+            // so we create Ceil((B-A)/XWidth)+1 small subintervals
+            //
+            ns = (int)Math.Ceiling(Math.Abs(state.b-state.a)/state.xwidth)+1;
+            state.heapsize = ns;
+            state.heapused = ns;
+            state.heapwidth = 5;
+            state.heap = new double[state.heapsize, state.heapwidth];
+            state.sumerr = 0;
+            state.sumabs = 0;
+            j = 0;
+        lbl_8:
+            if( j>ns-1 )
+            {
+                goto lbl_10;
+            }
+            ta = state.a+j*(state.b-state.a)/ns;
+            tb = state.a+(j+1)*(state.b-state.a)/ns;
+            c1 = 0.5*(tb-ta);
+            c2 = 0.5*(tb+ta);
+            intg = 0;
+            intk = 0;
+            inta = 0;
+            i = 0;
+        lbl_11:
+            if( i>state.n-1 )
+            {
+                goto lbl_13;
+            }
+            
+            //
+            // obtain F
+            //
+            state.x = c1*state.qn[i]+c2;
+            state.rstate.stage = 1;
+            goto lbl_rcomm;
+        lbl_1:
+            v = state.f;
+            
+            //
+            // Gauss-Kronrod formula
+            //
+            intk = intk+v*state.wk[i];
+            if( i%2==1 )
+            {
+                intg = intg+v*state.wg[i];
+            }
+            
+            //
+            // Integral |F(x)|
+            // Use rectangles method
+            //
+            inta = inta+Math.Abs(v)*state.wr[i];
+            i = i+1;
+            goto lbl_11;
+        lbl_13:
+            intk = intk*(tb-ta)*0.5;
+            intg = intg*(tb-ta)*0.5;
+            inta = inta*(tb-ta)*0.5;
+            state.heap[j,0] = Math.Abs(intg-intk);
+            state.heap[j,1] = intk;
+            state.heap[j,2] = inta;
+            state.heap[j,3] = ta;
+            state.heap[j,4] = tb;
+            state.sumerr = state.sumerr+state.heap[j,0];
+            state.sumabs = state.sumabs+Math.Abs(inta);
+            j = j+1;
+            goto lbl_8;
+        lbl_10:
+        lbl_4:
+            
+            //
+            // method iterations
+            //
+        lbl_14:
+            if( false )
+            {
+                goto lbl_15;
+            }
+            
+            //
+            // additional memory if needed
+            //
+            if( state.heapused==state.heapsize )
+            {
+                mheapresize(ref state.heap, ref state.heapsize, 4*state.heapsize, state.heapwidth);
+            }
+            
+            //
+            // TODO: every 20 iterations recalculate errors/sums
+            //
+            if( (double)(state.sumerr)<=(double)(state.eps*state.sumabs) || state.heapused>=maxsubintervals )
+            {
+                state.r = 0;
+                for(j=0; j<=state.heapused-1; j++)
+                {
+                    state.r = state.r+state.heap[j,1];
+                }
+                result = false;
+                return result;
+            }
+            
+            //
+            // Exclude interval with maximum absolute error
+            //
+            mheappop(ref state.heap, state.heapused, state.heapwidth);
+            state.sumerr = state.sumerr-state.heap[state.heapused-1,0];
+            state.sumabs = state.sumabs-state.heap[state.heapused-1,2];
+            
+            //
+            // Divide interval, create subintervals
+            //
+            ta = state.heap[state.heapused-1,3];
+            tb = state.heap[state.heapused-1,4];
+            state.heap[state.heapused-1,3] = ta;
+            state.heap[state.heapused-1,4] = 0.5*(ta+tb);
+            state.heap[state.heapused,3] = 0.5*(ta+tb);
+            state.heap[state.heapused,4] = tb;
+            j = state.heapused-1;
+        lbl_16:
+            if( j>state.heapused )
+            {
+                goto lbl_18;
+            }
+            c1 = 0.5*(state.heap[j,4]-state.heap[j,3]);
+            c2 = 0.5*(state.heap[j,4]+state.heap[j,3]);
+            intg = 0;
+            intk = 0;
+            inta = 0;
+            i = 0;
+        lbl_19:
+            if( i>state.n-1 )
+            {
+                goto lbl_21;
+            }
+            
+            //
+            // F(x)
+            //
+            state.x = c1*state.qn[i]+c2;
+            state.rstate.stage = 2;
+            goto lbl_rcomm;
+        lbl_2:
+            v = state.f;
+            
+            //
+            // Gauss-Kronrod formula
+            //
+            intk = intk+v*state.wk[i];
+            if( i%2==1 )
+            {
+                intg = intg+v*state.wg[i];
+            }
+            
+            //
+            // Integral |F(x)|
+            // Use rectangles method
+            //
+            inta = inta+Math.Abs(v)*state.wr[i];
+            i = i+1;
+            goto lbl_19;
+        lbl_21:
+            intk = intk*(state.heap[j,4]-state.heap[j,3])*0.5;
+            intg = intg*(state.heap[j,4]-state.heap[j,3])*0.5;
+            inta = inta*(state.heap[j,4]-state.heap[j,3])*0.5;
+            state.heap[j,0] = Math.Abs(intg-intk);
+            state.heap[j,1] = intk;
+            state.heap[j,2] = inta;
+            state.sumerr = state.sumerr+state.heap[j,0];
+            state.sumabs = state.sumabs+state.heap[j,2];
+            j = j+1;
+            goto lbl_16;
+        lbl_18:
+            mheappush(ref state.heap, state.heapused-1, state.heapwidth);
+            mheappush(ref state.heap, state.heapused, state.heapwidth);
+            state.heapused = state.heapused+1;
+            goto lbl_14;
+        lbl_15:
+            result = false;
+            return result;
+            
+            //
+            // Saving state
+            //
+        lbl_rcomm:
+            result = true;
+            state.rstate.ia[0] = i;
+            state.rstate.ia[1] = j;
+            state.rstate.ia[2] = ns;
+            state.rstate.ia[3] = info;
+            state.rstate.ra[0] = c1;
+            state.rstate.ra[1] = c2;
+            state.rstate.ra[2] = intg;
+            state.rstate.ra[3] = intk;
+            state.rstate.ra[4] = inta;
+            state.rstate.ra[5] = v;
+            state.rstate.ra[6] = ta;
+            state.rstate.ra[7] = tb;
+            state.rstate.ra[8] = qeps;
+            return result;
+        }
+
+
+        private static void mheappop(ref double[,] heap,
+            int heapsize,
+            int heapwidth)
+        {
+            int i = 0;
+            int p = 0;
+            double t = 0;
+            int maxcp = 0;
+
+            if( heapsize==1 )
+            {
+                return;
+            }
+            for(i=0; i<=heapwidth-1; i++)
+            {
+                t = heap[heapsize-1,i];
+                heap[heapsize-1,i] = heap[0,i];
+                heap[0,i] = t;
+            }
+            p = 0;
+            while( 2*p+1<heapsize-1 )
+            {
+                maxcp = 2*p+1;
+                if( 2*p+2<heapsize-1 )
+                {
+                    if( (double)(heap[2*p+2,0])>(double)(heap[2*p+1,0]) )
+                    {
+                        maxcp = 2*p+2;
+                    }
+                }
+                if( (double)(heap[p,0])<(double)(heap[maxcp,0]) )
+                {
+                    for(i=0; i<=heapwidth-1; i++)
+                    {
+                        t = heap[p,i];
+                        heap[p,i] = heap[maxcp,i];
+                        heap[maxcp,i] = t;
+                    }
+                    p = maxcp;
+                }
+                else
+                {
+                    break;
+                }
+            }
+        }
+
+
+        private static void mheappush(ref double[,] heap,
+            int heapsize,
+            int heapwidth)
+        {
+            int i = 0;
+            int p = 0;
+            double t = 0;
+            int parent = 0;
+
+            if( heapsize==0 )
+            {
+                return;
+            }
+            p = heapsize;
+            while( p!=0 )
+            {
+                parent = (p-1)/2;
+                if( (double)(heap[p,0])>(double)(heap[parent,0]) )
+                {
+                    for(i=0; i<=heapwidth-1; i++)
+                    {
+                        t = heap[p,i];
+                        heap[p,i] = heap[parent,i];
+                        heap[parent,i] = t;
+                    }
+                    p = parent;
+                }
+                else
+                {
+                    break;
+                }
+            }
+        }
+
+
+        private static void mheapresize(ref double[,] heap,
+            ref int heapsize,
+            int newheapsize,
+            int heapwidth)
+        {
+            double[,] tmp = new double[0,0];
+            int i = 0;
+            int i_ = 0;
+
+            tmp = new double[heapsize, heapwidth];
+            for(i=0; i<=heapsize-1; i++)
+            {
+                for(i_=0; i_<=heapwidth-1;i_++)
+                {
+                    tmp[i,i_] = heap[i,i_];
+                }
+            }
+            heap = new double[newheapsize, heapwidth];
+            for(i=0; i<=heapsize-1; i++)
+            {
+                for(i_=0; i_<=heapwidth-1;i_++)
+                {
+                    heap[i,i_] = tmp[i,i_];
+                }
+            }
+            heapsize = newheapsize;
+        }
+
+
+    }
+}
+
Index: /trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.5.0/ALGLIB-3.5.0/interpolation.cs
===================================================================
--- /trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.5.0/ALGLIB-3.5.0/interpolation.cs	(revision 7673)
+++ /trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.5.0/ALGLIB-3.5.0/interpolation.cs	(revision 7673)
@@ -0,0 +1,22063 @@
+/*************************************************************************
+Copyright (c) Sergey Bochkanov (ALGLIB project).
+
+>>> SOURCE LICENSE >>>
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation (www.fsf.org); either version 2 of the
+License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+A copy of the GNU General Public License is available at
+http://www.fsf.org/licensing/licenses
+>>> END OF LICENSE >>>
+*************************************************************************/
+#pragma warning disable 162
+#pragma warning disable 219
+using System;
+
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    IDW interpolant.
+    *************************************************************************/
+    public class idwinterpolant
+    {
+        //
+        // Public declarations
+        //
+
+        public idwinterpolant()
+        {
+            _innerobj = new idwint.idwinterpolant();
+        }
+
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private idwint.idwinterpolant _innerobj;
+        public idwint.idwinterpolant innerobj { get { return _innerobj; } }
+        public idwinterpolant(idwint.idwinterpolant obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+    /*************************************************************************
+    IDW interpolation
+
+    INPUT PARAMETERS:
+        Z   -   IDW interpolant built with one of model building
+                subroutines.
+        X   -   array[0..NX-1], interpolation point
+
+    Result:
+        IDW interpolant Z(X)
+
+      -- ALGLIB --
+         Copyright 02.03.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static double idwcalc(idwinterpolant z, double[] x)
+    {
+
+        double result = idwint.idwcalc(z.innerobj, x);
+        return result;
+    }
+
+    /*************************************************************************
+    IDW interpolant using modified Shepard method for uniform point
+    distributions.
+
+    INPUT PARAMETERS:
+        XY  -   X and Y values, array[0..N-1,0..NX].
+                First NX columns contain X-values, last column contain
+                Y-values.
+        N   -   number of nodes, N>0.
+        NX  -   space dimension, NX>=1.
+        D   -   nodal function type, either:
+                * 0     constant  model.  Just  for  demonstration only, worst
+                        model ever.
+                * 1     linear model, least squares fitting. Simpe  model  for
+                        datasets too small for quadratic models
+                * 2     quadratic  model,  least  squares  fitting. Best model
+                        available (if your dataset is large enough).
+                * -1    "fast"  linear  model,  use  with  caution!!!   It  is
+                        significantly  faster than linear/quadratic and better
+                        than constant model. But it is less robust (especially
+                        in the presence of noise).
+        NQ  -   number of points used to calculate  nodal  functions  (ignored
+                for constant models). NQ should be LARGER than:
+                * max(1.5*(1+NX),2^NX+1) for linear model,
+                * max(3/4*(NX+2)*(NX+1),2^NX+1) for quadratic model.
+                Values less than this threshold will be silently increased.
+        NW  -   number of points used to calculate weights and to interpolate.
+                Required: >=2^NX+1, values less than this  threshold  will  be
+                silently increased.
+                Recommended value: about 2*NQ
+
+    OUTPUT PARAMETERS:
+        Z   -   IDW interpolant.
+
+    NOTES:
+      * best results are obtained with quadratic models, worst - with constant
+        models
+      * when N is large, NQ and NW must be significantly smaller than  N  both
+        to obtain optimal performance and to obtain optimal accuracy. In 2  or
+        3-dimensional tasks NQ=15 and NW=25 are good values to start with.
+      * NQ  and  NW  may  be  greater  than  N.  In  such  cases  they will be
+        automatically decreased.
+      * this subroutine is always succeeds (as long as correct parameters  are
+        passed).
+      * see  'Multivariate  Interpolation  of Large Sets of Scattered Data' by
+        Robert J. Renka for more information on this algorithm.
+      * this subroutine assumes that point distribution is uniform at the small
+        scales.  If  it  isn't  -  for  example,  points are concentrated along
+        "lines", but "lines" distribution is uniform at the larger scale - then
+        you should use IDWBuildModifiedShepardR()
+
+
+      -- ALGLIB PROJECT --
+         Copyright 02.03.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void idwbuildmodifiedshepard(double[,] xy, int n, int nx, int d, int nq, int nw, out idwinterpolant z)
+    {
+        z = new idwinterpolant();
+        idwint.idwbuildmodifiedshepard(xy, n, nx, d, nq, nw, z.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    IDW interpolant using modified Shepard method for non-uniform datasets.
+
+    This type of model uses  constant  nodal  functions and interpolates using
+    all nodes which are closer than user-specified radius R. It  may  be  used
+    when points distribution is non-uniform at the small scale, but it  is  at
+    the distances as large as R.
+
+    INPUT PARAMETERS:
+        XY  -   X and Y values, array[0..N-1,0..NX].
+                First NX columns contain X-values, last column contain
+                Y-values.
+        N   -   number of nodes, N>0.
+        NX  -   space dimension, NX>=1.
+        R   -   radius, R>0
+
+    OUTPUT PARAMETERS:
+        Z   -   IDW interpolant.
+
+    NOTES:
+    * if there is less than IDWKMin points within  R-ball,  algorithm  selects
+      IDWKMin closest ones, so that continuity properties of  interpolant  are
+      preserved even far from points.
+
+      -- ALGLIB PROJECT --
+         Copyright 11.04.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void idwbuildmodifiedshepardr(double[,] xy, int n, int nx, double r, out idwinterpolant z)
+    {
+        z = new idwinterpolant();
+        idwint.idwbuildmodifiedshepardr(xy, n, nx, r, z.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    IDW model for noisy data.
+
+    This subroutine may be used to handle noisy data, i.e. data with noise  in
+    OUTPUT values.  It differs from IDWBuildModifiedShepard() in the following
+    aspects:
+    * nodal functions are not constrained to pass through  nodes:  Qi(xi)<>yi,
+      i.e. we have fitting  instead  of  interpolation.
+    * weights which are used during least  squares fitting stage are all equal
+      to 1.0 (independently of distance)
+    * "fast"-linear or constant nodal functions are not supported (either  not
+      robust enough or too rigid)
+
+    This problem require far more complex tuning than interpolation  problems.
+    Below you can find some recommendations regarding this problem:
+    * focus on tuning NQ; it controls noise reduction. As for NW, you can just
+      make it equal to 2*NQ.
+    * you can use cross-validation to determine optimal NQ.
+    * optimal NQ is a result of complex tradeoff  between  noise  level  (more
+      noise = larger NQ required) and underlying  function  complexity  (given
+      fixed N, larger NQ means smoothing of compex features in the data).  For
+      example, NQ=N will reduce noise to the minimum level possible,  but  you
+      will end up with just constant/linear/quadratic (depending on  D)  least
+      squares model for the whole dataset.
+
+    INPUT PARAMETERS:
+        XY  -   X and Y values, array[0..N-1,0..NX].
+                First NX columns contain X-values, last column contain
+                Y-values.
+        N   -   number of nodes, N>0.
+        NX  -   space dimension, NX>=1.
+        D   -   nodal function degree, either:
+                * 1     linear model, least squares fitting. Simpe  model  for
+                        datasets too small for quadratic models (or  for  very
+                        noisy problems).
+                * 2     quadratic  model,  least  squares  fitting. Best model
+                        available (if your dataset is large enough).
+        NQ  -   number of points used to calculate nodal functions.  NQ should
+                be  significantly   larger   than  1.5  times  the  number  of
+                coefficients in a nodal function to overcome effects of noise:
+                * larger than 1.5*(1+NX) for linear model,
+                * larger than 3/4*(NX+2)*(NX+1) for quadratic model.
+                Values less than this threshold will be silently increased.
+        NW  -   number of points used to calculate weights and to interpolate.
+                Required: >=2^NX+1, values less than this  threshold  will  be
+                silently increased.
+                Recommended value: about 2*NQ or larger
+
+    OUTPUT PARAMETERS:
+        Z   -   IDW interpolant.
+
+    NOTES:
+      * best results are obtained with quadratic models, linear models are not
+        recommended to use unless you are pretty sure that it is what you want
+      * this subroutine is always succeeds (as long as correct parameters  are
+        passed).
+      * see  'Multivariate  Interpolation  of Large Sets of Scattered Data' by
+        Robert J. Renka for more information on this algorithm.
+
+
+      -- ALGLIB PROJECT --
+         Copyright 02.03.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void idwbuildnoisy(double[,] xy, int n, int nx, int d, int nq, int nw, out idwinterpolant z)
+    {
+        z = new idwinterpolant();
+        idwint.idwbuildnoisy(xy, n, nx, d, nq, nw, z.innerobj);
+        return;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    Barycentric interpolant.
+    *************************************************************************/
+    public class barycentricinterpolant
+    {
+        //
+        // Public declarations
+        //
+
+        public barycentricinterpolant()
+        {
+            _innerobj = new ratint.barycentricinterpolant();
+        }
+
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private ratint.barycentricinterpolant _innerobj;
+        public ratint.barycentricinterpolant innerobj { get { return _innerobj; } }
+        public barycentricinterpolant(ratint.barycentricinterpolant obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+    /*************************************************************************
+    Rational interpolation using barycentric formula
+
+    F(t) = SUM(i=0,n-1,w[i]*f[i]/(t-x[i])) / SUM(i=0,n-1,w[i]/(t-x[i]))
+
+    Input parameters:
+        B   -   barycentric interpolant built with one of model building
+                subroutines.
+        T   -   interpolation point
+
+    Result:
+        barycentric interpolant F(t)
+
+      -- ALGLIB --
+         Copyright 17.08.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static double barycentriccalc(barycentricinterpolant b, double t)
+    {
+
+        double result = ratint.barycentriccalc(b.innerobj, t);
+        return result;
+    }
+
+    /*************************************************************************
+    Differentiation of barycentric interpolant: first derivative.
+
+    Algorithm used in this subroutine is very robust and should not fail until
+    provided with values too close to MaxRealNumber  (usually  MaxRealNumber/N
+    or greater will overflow).
+
+    INPUT PARAMETERS:
+        B   -   barycentric interpolant built with one of model building
+                subroutines.
+        T   -   interpolation point
+
+    OUTPUT PARAMETERS:
+        F   -   barycentric interpolant at T
+        DF  -   first derivative
+
+    NOTE
+
+
+      -- ALGLIB --
+         Copyright 17.08.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void barycentricdiff1(barycentricinterpolant b, double t, out double f, out double df)
+    {
+        f = 0;
+        df = 0;
+        ratint.barycentricdiff1(b.innerobj, t, ref f, ref df);
+        return;
+    }
+
+    /*************************************************************************
+    Differentiation of barycentric interpolant: first/second derivatives.
+
+    INPUT PARAMETERS:
+        B   -   barycentric interpolant built with one of model building
+                subroutines.
+        T   -   interpolation point
+
+    OUTPUT PARAMETERS:
+        F   -   barycentric interpolant at T
+        DF  -   first derivative
+        D2F -   second derivative
+
+    NOTE: this algorithm may fail due to overflow/underflor if  used  on  data
+    whose values are close to MaxRealNumber or MinRealNumber.  Use more robust
+    BarycentricDiff1() subroutine in such cases.
+
+
+      -- ALGLIB --
+         Copyright 17.08.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void barycentricdiff2(barycentricinterpolant b, double t, out double f, out double df, out double d2f)
+    {
+        f = 0;
+        df = 0;
+        d2f = 0;
+        ratint.barycentricdiff2(b.innerobj, t, ref f, ref df, ref d2f);
+        return;
+    }
+
+    /*************************************************************************
+    This subroutine performs linear transformation of the argument.
+
+    INPUT PARAMETERS:
+        B       -   rational interpolant in barycentric form
+        CA, CB  -   transformation coefficients: x = CA*t + CB
+
+    OUTPUT PARAMETERS:
+        B       -   transformed interpolant with X replaced by T
+
+      -- ALGLIB PROJECT --
+         Copyright 19.08.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void barycentriclintransx(barycentricinterpolant b, double ca, double cb)
+    {
+
+        ratint.barycentriclintransx(b.innerobj, ca, cb);
+        return;
+    }
+
+    /*************************************************************************
+    This  subroutine   performs   linear  transformation  of  the  barycentric
+    interpolant.
+
+    INPUT PARAMETERS:
+        B       -   rational interpolant in barycentric form
+        CA, CB  -   transformation coefficients: B2(x) = CA*B(x) + CB
+
+    OUTPUT PARAMETERS:
+        B       -   transformed interpolant
+
+      -- ALGLIB PROJECT --
+         Copyright 19.08.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void barycentriclintransy(barycentricinterpolant b, double ca, double cb)
+    {
+
+        ratint.barycentriclintransy(b.innerobj, ca, cb);
+        return;
+    }
+
+    /*************************************************************************
+    Extracts X/Y/W arrays from rational interpolant
+
+    INPUT PARAMETERS:
+        B   -   barycentric interpolant
+
+    OUTPUT PARAMETERS:
+        N   -   nodes count, N>0
+        X   -   interpolation nodes, array[0..N-1]
+        F   -   function values, array[0..N-1]
+        W   -   barycentric weights, array[0..N-1]
+
+      -- ALGLIB --
+         Copyright 17.08.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void barycentricunpack(barycentricinterpolant b, out int n, out double[] x, out double[] y, out double[] w)
+    {
+        n = 0;
+        x = new double[0];
+        y = new double[0];
+        w = new double[0];
+        ratint.barycentricunpack(b.innerobj, ref n, ref x, ref y, ref w);
+        return;
+    }
+
+    /*************************************************************************
+    Rational interpolant from X/Y/W arrays
+
+    F(t) = SUM(i=0,n-1,w[i]*f[i]/(t-x[i])) / SUM(i=0,n-1,w[i]/(t-x[i]))
+
+    INPUT PARAMETERS:
+        X   -   interpolation nodes, array[0..N-1]
+        F   -   function values, array[0..N-1]
+        W   -   barycentric weights, array[0..N-1]
+        N   -   nodes count, N>0
+
+    OUTPUT PARAMETERS:
+        B   -   barycentric interpolant built from (X, Y, W)
+
+      -- ALGLIB --
+         Copyright 17.08.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void barycentricbuildxyw(double[] x, double[] y, double[] w, int n, out barycentricinterpolant b)
+    {
+        b = new barycentricinterpolant();
+        ratint.barycentricbuildxyw(x, y, w, n, b.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    Rational interpolant without poles
+
+    The subroutine constructs the rational interpolating function without real
+    poles  (see  'Barycentric rational interpolation with no  poles  and  high
+    rates of approximation', Michael S. Floater. and  Kai  Hormann,  for  more
+    information on this subject).
+
+    Input parameters:
+        X   -   interpolation nodes, array[0..N-1].
+        Y   -   function values, array[0..N-1].
+        N   -   number of nodes, N>0.
+        D   -   order of the interpolation scheme, 0 <= D <= N-1.
+                D<0 will cause an error.
+                D>=N it will be replaced with D=N-1.
+                if you don't know what D to choose, use small value about 3-5.
+
+    Output parameters:
+        B   -   barycentric interpolant.
+
+    Note:
+        this algorithm always succeeds and calculates the weights  with  close
+        to machine precision.
+
+      -- ALGLIB PROJECT --
+         Copyright 17.06.2007 by Bochkanov Sergey
+    *************************************************************************/
+    public static void barycentricbuildfloaterhormann(double[] x, double[] y, int n, int d, out barycentricinterpolant b)
+    {
+        b = new barycentricinterpolant();
+        ratint.barycentricbuildfloaterhormann(x, y, n, d, b.innerobj);
+        return;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    Conversion from barycentric representation to Chebyshev basis.
+    This function has O(N^2) complexity.
+
+    INPUT PARAMETERS:
+        P   -   polynomial in barycentric form
+        A,B -   base interval for Chebyshev polynomials (see below)
+                A<>B
+
+    OUTPUT PARAMETERS
+        T   -   coefficients of Chebyshev representation;
+                P(x) = sum { T[i]*Ti(2*(x-A)/(B-A)-1), i=0..N-1 },
+                where Ti - I-th Chebyshev polynomial.
+
+    NOTES:
+        barycentric interpolant passed as P may be either polynomial  obtained
+        from  polynomial  interpolation/ fitting or rational function which is
+        NOT polynomial. We can't distinguish between these two cases, and this
+        algorithm just tries to work assuming that P IS a polynomial.  If not,
+        algorithm will return results, but they won't have any meaning.
+
+      -- ALGLIB --
+         Copyright 30.09.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void polynomialbar2cheb(barycentricinterpolant p, double a, double b, out double[] t)
+    {
+        t = new double[0];
+        polint.polynomialbar2cheb(p.innerobj, a, b, ref t);
+        return;
+    }
+
+    /*************************************************************************
+    Conversion from Chebyshev basis to barycentric representation.
+    This function has O(N^2) complexity.
+
+    INPUT PARAMETERS:
+        T   -   coefficients of Chebyshev representation;
+                P(x) = sum { T[i]*Ti(2*(x-A)/(B-A)-1), i=0..N },
+                where Ti - I-th Chebyshev polynomial.
+        N   -   number of coefficients:
+                * if given, only leading N elements of T are used
+                * if not given, automatically determined from size of T
+        A,B -   base interval for Chebyshev polynomials (see above)
+                A<B
+
+    OUTPUT PARAMETERS
+        P   -   polynomial in barycentric form
+
+      -- ALGLIB --
+         Copyright 30.09.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void polynomialcheb2bar(double[] t, int n, double a, double b, out barycentricinterpolant p)
+    {
+        p = new barycentricinterpolant();
+        polint.polynomialcheb2bar(t, n, a, b, p.innerobj);
+        return;
+    }
+    public static void polynomialcheb2bar(double[] t, double a, double b, out barycentricinterpolant p)
+    {
+        int n;
+
+        p = new barycentricinterpolant();
+        n = ap.len(t);
+        polint.polynomialcheb2bar(t, n, a, b, p.innerobj);
+
+        return;
+    }
+
+    /*************************************************************************
+    Conversion from barycentric representation to power basis.
+    This function has O(N^2) complexity.
+
+    INPUT PARAMETERS:
+        P   -   polynomial in barycentric form
+        C   -   offset (see below); 0.0 is used as default value.
+        S   -   scale (see below);  1.0 is used as default value. S<>0.
+
+    OUTPUT PARAMETERS
+        A   -   coefficients, P(x) = sum { A[i]*((X-C)/S)^i, i=0..N-1 }
+        N   -   number of coefficients (polynomial degree plus 1)
+
+    NOTES:
+    1.  this function accepts offset and scale, which can be  set  to  improve
+        numerical properties of polynomial. For example, if P was obtained  as
+        result of interpolation on [-1,+1],  you  can  set  C=0  and  S=1  and
+        represent  P  as sum of 1, x, x^2, x^3 and so on. In most cases you it
+        is exactly what you need.
+
+        However, if your interpolation model was built on [999,1001], you will
+        see significant growth of numerical errors when using {1, x, x^2, x^3}
+        as basis. Representing P as sum of 1, (x-1000), (x-1000)^2, (x-1000)^3
+        will be better option. Such representation can be  obtained  by  using
+        1000.0 as offset C and 1.0 as scale S.
+
+    2.  power basis is ill-conditioned and tricks described above can't  solve
+        this problem completely. This function  will  return  coefficients  in
+        any  case,  but  for  N>8  they  will  become unreliable. However, N's
+        less than 5 are pretty safe.
+
+    3.  barycentric interpolant passed as P may be either polynomial  obtained
+        from  polynomial  interpolation/ fitting or rational function which is
+        NOT polynomial. We can't distinguish between these two cases, and this
+        algorithm just tries to work assuming that P IS a polynomial.  If not,
+        algorithm will return results, but they won't have any meaning.
+
+      -- ALGLIB --
+         Copyright 30.09.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void polynomialbar2pow(barycentricinterpolant p, double c, double s, out double[] a)
+    {
+        a = new double[0];
+        polint.polynomialbar2pow(p.innerobj, c, s, ref a);
+        return;
+    }
+    public static void polynomialbar2pow(barycentricinterpolant p, out double[] a)
+    {
+        double c;
+        double s;
+
+        a = new double[0];
+        c = 0;
+        s = 1;
+        polint.polynomialbar2pow(p.innerobj, c, s, ref a);
+
+        return;
+    }
+
+    /*************************************************************************
+    Conversion from power basis to barycentric representation.
+    This function has O(N^2) complexity.
+
+    INPUT PARAMETERS:
+        A   -   coefficients, P(x) = sum { A[i]*((X-C)/S)^i, i=0..N-1 }
+        N   -   number of coefficients (polynomial degree plus 1)
+                * if given, only leading N elements of A are used
+                * if not given, automatically determined from size of A
+        C   -   offset (see below); 0.0 is used as default value.
+        S   -   scale (see below);  1.0 is used as default value. S<>0.
+
+    OUTPUT PARAMETERS
+        P   -   polynomial in barycentric form
+
+
+    NOTES:
+    1.  this function accepts offset and scale, which can be  set  to  improve
+        numerical properties of polynomial. For example, if you interpolate on
+        [-1,+1],  you  can  set C=0 and S=1 and convert from sum of 1, x, x^2,
+        x^3 and so on. In most cases you it is exactly what you need.
+
+        However, if your interpolation model was built on [999,1001], you will
+        see significant growth of numerical errors when using {1, x, x^2, x^3}
+        as  input  basis.  Converting  from  sum  of  1, (x-1000), (x-1000)^2,
+        (x-1000)^3 will be better option (you have to specify 1000.0 as offset
+        C and 1.0 as scale S).
+
+    2.  power basis is ill-conditioned and tricks described above can't  solve
+        this problem completely. This function  will  return barycentric model
+        in any case, but for N>8 accuracy well degrade. However, N's less than
+        5 are pretty safe.
+
+      -- ALGLIB --
+         Copyright 30.09.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void polynomialpow2bar(double[] a, int n, double c, double s, out barycentricinterpolant p)
+    {
+        p = new barycentricinterpolant();
+        polint.polynomialpow2bar(a, n, c, s, p.innerobj);
+        return;
+    }
+    public static void polynomialpow2bar(double[] a, out barycentricinterpolant p)
+    {
+        int n;
+        double c;
+        double s;
+
+        p = new barycentricinterpolant();
+        n = ap.len(a);
+        c = 0;
+        s = 1;
+        polint.polynomialpow2bar(a, n, c, s, p.innerobj);
+
+        return;
+    }
+
+    /*************************************************************************
+    Lagrange intepolant: generation of the model on the general grid.
+    This function has O(N^2) complexity.
+
+    INPUT PARAMETERS:
+        X   -   abscissas, array[0..N-1]
+        Y   -   function values, array[0..N-1]
+        N   -   number of points, N>=1
+
+    OUTPUT PARAMETERS
+        P   -   barycentric model which represents Lagrange interpolant
+                (see ratint unit info and BarycentricCalc() description for
+                more information).
+
+      -- ALGLIB --
+         Copyright 02.12.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void polynomialbuild(double[] x, double[] y, int n, out barycentricinterpolant p)
+    {
+        p = new barycentricinterpolant();
+        polint.polynomialbuild(x, y, n, p.innerobj);
+        return;
+    }
+    public static void polynomialbuild(double[] x, double[] y, out barycentricinterpolant p)
+    {
+        int n;
+        if( (ap.len(x)!=ap.len(y)))
+            throw new alglibexception("Error while calling 'polynomialbuild': looks like one of arguments has wrong size");
+        p = new barycentricinterpolant();
+        n = ap.len(x);
+        polint.polynomialbuild(x, y, n, p.innerobj);
+
+        return;
+    }
+
+    /*************************************************************************
+    Lagrange intepolant: generation of the model on equidistant grid.
+    This function has O(N) complexity.
+
+    INPUT PARAMETERS:
+        A   -   left boundary of [A,B]
+        B   -   right boundary of [A,B]
+        Y   -   function values at the nodes, array[0..N-1]
+        N   -   number of points, N>=1
+                for N=1 a constant model is constructed.
+
+    OUTPUT PARAMETERS
+        P   -   barycentric model which represents Lagrange interpolant
+                (see ratint unit info and BarycentricCalc() description for
+                more information).
+
+      -- ALGLIB --
+         Copyright 03.12.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void polynomialbuildeqdist(double a, double b, double[] y, int n, out barycentricinterpolant p)
+    {
+        p = new barycentricinterpolant();
+        polint.polynomialbuildeqdist(a, b, y, n, p.innerobj);
+        return;
+    }
+    public static void polynomialbuildeqdist(double a, double b, double[] y, out barycentricinterpolant p)
+    {
+        int n;
+
+        p = new barycentricinterpolant();
+        n = ap.len(y);
+        polint.polynomialbuildeqdist(a, b, y, n, p.innerobj);
+
+        return;
+    }
+
+    /*************************************************************************
+    Lagrange intepolant on Chebyshev grid (first kind).
+    This function has O(N) complexity.
+
+    INPUT PARAMETERS:
+        A   -   left boundary of [A,B]
+        B   -   right boundary of [A,B]
+        Y   -   function values at the nodes, array[0..N-1],
+                Y[I] = Y(0.5*(B+A) + 0.5*(B-A)*Cos(PI*(2*i+1)/(2*n)))
+        N   -   number of points, N>=1
+                for N=1 a constant model is constructed.
+
+    OUTPUT PARAMETERS
+        P   -   barycentric model which represents Lagrange interpolant
+                (see ratint unit info and BarycentricCalc() description for
+                more information).
+
+      -- ALGLIB --
+         Copyright 03.12.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void polynomialbuildcheb1(double a, double b, double[] y, int n, out barycentricinterpolant p)
+    {
+        p = new barycentricinterpolant();
+        polint.polynomialbuildcheb1(a, b, y, n, p.innerobj);
+        return;
+    }
+    public static void polynomialbuildcheb1(double a, double b, double[] y, out barycentricinterpolant p)
+    {
+        int n;
+
+        p = new barycentricinterpolant();
+        n = ap.len(y);
+        polint.polynomialbuildcheb1(a, b, y, n, p.innerobj);
+
+        return;
+    }
+
+    /*************************************************************************
+    Lagrange intepolant on Chebyshev grid (second kind).
+    This function has O(N) complexity.
+
+    INPUT PARAMETERS:
+        A   -   left boundary of [A,B]
+        B   -   right boundary of [A,B]
+        Y   -   function values at the nodes, array[0..N-1],
+                Y[I] = Y(0.5*(B+A) + 0.5*(B-A)*Cos(PI*i/(n-1)))
+        N   -   number of points, N>=1
+                for N=1 a constant model is constructed.
+
+    OUTPUT PARAMETERS
+        P   -   barycentric model which represents Lagrange interpolant
+                (see ratint unit info and BarycentricCalc() description for
+                more information).
+
+      -- ALGLIB --
+         Copyright 03.12.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void polynomialbuildcheb2(double a, double b, double[] y, int n, out barycentricinterpolant p)
+    {
+        p = new barycentricinterpolant();
+        polint.polynomialbuildcheb2(a, b, y, n, p.innerobj);
+        return;
+    }
+    public static void polynomialbuildcheb2(double a, double b, double[] y, out barycentricinterpolant p)
+    {
+        int n;
+
+        p = new barycentricinterpolant();
+        n = ap.len(y);
+        polint.polynomialbuildcheb2(a, b, y, n, p.innerobj);
+
+        return;
+    }
+
+    /*************************************************************************
+    Fast equidistant polynomial interpolation function with O(N) complexity
+
+    INPUT PARAMETERS:
+        A   -   left boundary of [A,B]
+        B   -   right boundary of [A,B]
+        F   -   function values, array[0..N-1]
+        N   -   number of points on equidistant grid, N>=1
+                for N=1 a constant model is constructed.
+        T   -   position where P(x) is calculated
+
+    RESULT
+        value of the Lagrange interpolant at T
+
+    IMPORTANT
+        this function provides fast interface which is not overflow-safe
+        nor it is very precise.
+        the best option is to use  PolynomialBuildEqDist()/BarycentricCalc()
+        subroutines unless you are pretty sure that your data will not result
+        in overflow.
+
+      -- ALGLIB --
+         Copyright 02.12.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static double polynomialcalceqdist(double a, double b, double[] f, int n, double t)
+    {
+
+        double result = polint.polynomialcalceqdist(a, b, f, n, t);
+        return result;
+    }
+    public static double polynomialcalceqdist(double a, double b, double[] f, double t)
+    {
+        int n;
+
+
+        n = ap.len(f);
+        double result = polint.polynomialcalceqdist(a, b, f, n, t);
+
+        return result;
+    }
+
+    /*************************************************************************
+    Fast polynomial interpolation function on Chebyshev points (first kind)
+    with O(N) complexity.
+
+    INPUT PARAMETERS:
+        A   -   left boundary of [A,B]
+        B   -   right boundary of [A,B]
+        F   -   function values, array[0..N-1]
+        N   -   number of points on Chebyshev grid (first kind),
+                X[i] = 0.5*(B+A) + 0.5*(B-A)*Cos(PI*(2*i+1)/(2*n))
+                for N=1 a constant model is constructed.
+        T   -   position where P(x) is calculated
+
+    RESULT
+        value of the Lagrange interpolant at T
+
+    IMPORTANT
+        this function provides fast interface which is not overflow-safe
+        nor it is very precise.
+        the best option is to use  PolIntBuildCheb1()/BarycentricCalc()
+        subroutines unless you are pretty sure that your data will not result
+        in overflow.
+
+      -- ALGLIB --
+         Copyright 02.12.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static double polynomialcalccheb1(double a, double b, double[] f, int n, double t)
+    {
+
+        double result = polint.polynomialcalccheb1(a, b, f, n, t);
+        return result;
+    }
+    public static double polynomialcalccheb1(double a, double b, double[] f, double t)
+    {
+        int n;
+
+
+        n = ap.len(f);
+        double result = polint.polynomialcalccheb1(a, b, f, n, t);
+
+        return result;
+    }
+
+    /*************************************************************************
+    Fast polynomial interpolation function on Chebyshev points (second kind)
+    with O(N) complexity.
+
+    INPUT PARAMETERS:
+        A   -   left boundary of [A,B]
+        B   -   right boundary of [A,B]
+        F   -   function values, array[0..N-1]
+        N   -   number of points on Chebyshev grid (second kind),
+                X[i] = 0.5*(B+A) + 0.5*(B-A)*Cos(PI*i/(n-1))
+                for N=1 a constant model is constructed.
+        T   -   position where P(x) is calculated
+
+    RESULT
+        value of the Lagrange interpolant at T
+
+    IMPORTANT
+        this function provides fast interface which is not overflow-safe
+        nor it is very precise.
+        the best option is to use PolIntBuildCheb2()/BarycentricCalc()
+        subroutines unless you are pretty sure that your data will not result
+        in overflow.
+
+      -- ALGLIB --
+         Copyright 02.12.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static double polynomialcalccheb2(double a, double b, double[] f, int n, double t)
+    {
+
+        double result = polint.polynomialcalccheb2(a, b, f, n, t);
+        return result;
+    }
+    public static double polynomialcalccheb2(double a, double b, double[] f, double t)
+    {
+        int n;
+
+
+        n = ap.len(f);
+        double result = polint.polynomialcalccheb2(a, b, f, n, t);
+
+        return result;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    1-dimensional spline interpolant
+    *************************************************************************/
+    public class spline1dinterpolant
+    {
+        //
+        // Public declarations
+        //
+
+        public spline1dinterpolant()
+        {
+            _innerobj = new spline1d.spline1dinterpolant();
+        }
+
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private spline1d.spline1dinterpolant _innerobj;
+        public spline1d.spline1dinterpolant innerobj { get { return _innerobj; } }
+        public spline1dinterpolant(spline1d.spline1dinterpolant obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+    /*************************************************************************
+    This subroutine builds linear spline interpolant
+
+    INPUT PARAMETERS:
+        X   -   spline nodes, array[0..N-1]
+        Y   -   function values, array[0..N-1]
+        N   -   points count (optional):
+                * N>=2
+                * if given, only first N points are used to build spline
+                * if not given, automatically detected from X/Y sizes
+                  (len(X) must be equal to len(Y))
+
+    OUTPUT PARAMETERS:
+        C   -   spline interpolant
+
+
+    ORDER OF POINTS
+
+    Subroutine automatically sorts points, so caller may pass unsorted array.
+
+      -- ALGLIB PROJECT --
+         Copyright 24.06.2007 by Bochkanov Sergey
+    *************************************************************************/
+    public static void spline1dbuildlinear(double[] x, double[] y, int n, out spline1dinterpolant c)
+    {
+        c = new spline1dinterpolant();
+        spline1d.spline1dbuildlinear(x, y, n, c.innerobj);
+        return;
+    }
+    public static void spline1dbuildlinear(double[] x, double[] y, out spline1dinterpolant c)
+    {
+        int n;
+        if( (ap.len(x)!=ap.len(y)))
+            throw new alglibexception("Error while calling 'spline1dbuildlinear': looks like one of arguments has wrong size");
+        c = new spline1dinterpolant();
+        n = ap.len(x);
+        spline1d.spline1dbuildlinear(x, y, n, c.innerobj);
+
+        return;
+    }
+
+    /*************************************************************************
+    This subroutine builds cubic spline interpolant.
+
+    INPUT PARAMETERS:
+        X           -   spline nodes, array[0..N-1].
+        Y           -   function values, array[0..N-1].
+
+    OPTIONAL PARAMETERS:
+        N           -   points count:
+                        * N>=2
+                        * if given, only first N points are used to build spline
+                        * if not given, automatically detected from X/Y sizes
+                          (len(X) must be equal to len(Y))
+        BoundLType  -   boundary condition type for the left boundary
+        BoundL      -   left boundary condition (first or second derivative,
+                        depending on the BoundLType)
+        BoundRType  -   boundary condition type for the right boundary
+        BoundR      -   right boundary condition (first or second derivative,
+                        depending on the BoundRType)
+
+    OUTPUT PARAMETERS:
+        C           -   spline interpolant
+
+    ORDER OF POINTS
+
+    Subroutine automatically sorts points, so caller may pass unsorted array.
+
+    SETTING BOUNDARY VALUES:
+
+    The BoundLType/BoundRType parameters can have the following values:
+        * -1, which corresonds to the periodic (cyclic) boundary conditions.
+              In this case:
+              * both BoundLType and BoundRType must be equal to -1.
+              * BoundL/BoundR are ignored
+              * Y[last] is ignored (it is assumed to be equal to Y[first]).
+        *  0, which  corresponds  to  the  parabolically   terminated  spline
+              (BoundL and/or BoundR are ignored).
+        *  1, which corresponds to the first derivative boundary condition
+        *  2, which corresponds to the second derivative boundary condition
+        *  by default, BoundType=0 is used
+
+    PROBLEMS WITH PERIODIC BOUNDARY CONDITIONS:
+
+    Problems with periodic boundary conditions have Y[first_point]=Y[last_point].
+    However, this subroutine doesn't require you to specify equal  values  for
+    the first and last points - it automatically forces them  to  be  equal by
+    copying  Y[first_point]  (corresponds  to the leftmost,  minimal  X[])  to
+    Y[last_point]. However it is recommended to pass consistent values of Y[],
+    i.e. to make Y[first_point]=Y[last_point].
+
+      -- ALGLIB PROJECT --
+         Copyright 23.06.2007 by Bochkanov Sergey
+    *************************************************************************/
+    public static void spline1dbuildcubic(double[] x, double[] y, int n, int boundltype, double boundl, int boundrtype, double boundr, out spline1dinterpolant c)
+    {
+        c = new spline1dinterpolant();
+        spline1d.spline1dbuildcubic(x, y, n, boundltype, boundl, boundrtype, boundr, c.innerobj);
+        return;
+    }
+    public static void spline1dbuildcubic(double[] x, double[] y, out spline1dinterpolant c)
+    {
+        int n;
+        int boundltype;
+        double boundl;
+        int boundrtype;
+        double boundr;
+        if( (ap.len(x)!=ap.len(y)))
+            throw new alglibexception("Error while calling 'spline1dbuildcubic': looks like one of arguments has wrong size");
+        c = new spline1dinterpolant();
+        n = ap.len(x);
+        boundltype = 0;
+        boundl = 0;
+        boundrtype = 0;
+        boundr = 0;
+        spline1d.spline1dbuildcubic(x, y, n, boundltype, boundl, boundrtype, boundr, c.innerobj);
+
+        return;
+    }
+
+    /*************************************************************************
+    This function solves following problem: given table y[] of function values
+    at nodes x[], it calculates and returns table of function derivatives  d[]
+    (calculated at the same nodes x[]).
+
+    This function yields same result as Spline1DBuildCubic() call followed  by
+    sequence of Spline1DDiff() calls, but it can be several times faster  when
+    called for ordered X[] and X2[].
+
+    INPUT PARAMETERS:
+        X           -   spline nodes
+        Y           -   function values
+
+    OPTIONAL PARAMETERS:
+        N           -   points count:
+                        * N>=2
+                        * if given, only first N points are used
+                        * if not given, automatically detected from X/Y sizes
+                          (len(X) must be equal to len(Y))
+        BoundLType  -   boundary condition type for the left boundary
+        BoundL      -   left boundary condition (first or second derivative,
+                        depending on the BoundLType)
+        BoundRType  -   boundary condition type for the right boundary
+        BoundR      -   right boundary condition (first or second derivative,
+                        depending on the BoundRType)
+
+    OUTPUT PARAMETERS:
+        D           -   derivative values at X[]
+
+    ORDER OF POINTS
+
+    Subroutine automatically sorts points, so caller may pass unsorted array.
+    Derivative values are correctly reordered on return, so  D[I]  is  always
+    equal to S'(X[I]) independently of points order.
+
+    SETTING BOUNDARY VALUES:
+
+    The BoundLType/BoundRType parameters can have the following values:
+        * -1, which corresonds to the periodic (cyclic) boundary conditions.
+              In this case:
+              * both BoundLType and BoundRType must be equal to -1.
+              * BoundL/BoundR are ignored
+              * Y[last] is ignored (it is assumed to be equal to Y[first]).
+        *  0, which  corresponds  to  the  parabolically   terminated  spline
+              (BoundL and/or BoundR are ignored).
+        *  1, which corresponds to the first derivative boundary condition
+        *  2, which corresponds to the second derivative boundary condition
+        *  by default, BoundType=0 is used
+
+    PROBLEMS WITH PERIODIC BOUNDARY CONDITIONS:
+
+    Problems with periodic boundary conditions have Y[first_point]=Y[last_point].
+    However, this subroutine doesn't require you to specify equal  values  for
+    the first and last points - it automatically forces them  to  be  equal by
+    copying  Y[first_point]  (corresponds  to the leftmost,  minimal  X[])  to
+    Y[last_point]. However it is recommended to pass consistent values of Y[],
+    i.e. to make Y[first_point]=Y[last_point].
+
+      -- ALGLIB PROJECT --
+         Copyright 03.09.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void spline1dgriddiffcubic(double[] x, double[] y, int n, int boundltype, double boundl, int boundrtype, double boundr, out double[] d)
+    {
+        d = new double[0];
+        spline1d.spline1dgriddiffcubic(x, y, n, boundltype, boundl, boundrtype, boundr, ref d);
+        return;
+    }
+    public static void spline1dgriddiffcubic(double[] x, double[] y, out double[] d)
+    {
+        int n;
+        int boundltype;
+        double boundl;
+        int boundrtype;
+        double boundr;
+        if( (ap.len(x)!=ap.len(y)))
+            throw new alglibexception("Error while calling 'spline1dgriddiffcubic': looks like one of arguments has wrong size");
+        d = new double[0];
+        n = ap.len(x);
+        boundltype = 0;
+        boundl = 0;
+        boundrtype = 0;
+        boundr = 0;
+        spline1d.spline1dgriddiffcubic(x, y, n, boundltype, boundl, boundrtype, boundr, ref d);
+
+        return;
+    }
+
+    /*************************************************************************
+    This function solves following problem: given table y[] of function values
+    at  nodes  x[],  it  calculates  and  returns  tables  of first and second
+    function derivatives d1[] and d2[] (calculated at the same nodes x[]).
+
+    This function yields same result as Spline1DBuildCubic() call followed  by
+    sequence of Spline1DDiff() calls, but it can be several times faster  when
+    called for ordered X[] and X2[].
+
+    INPUT PARAMETERS:
+        X           -   spline nodes
+        Y           -   function values
+
+    OPTIONAL PARAMETERS:
+        N           -   points count:
+                        * N>=2
+                        * if given, only first N points are used
+                        * if not given, automatically detected from X/Y sizes
+                          (len(X) must be equal to len(Y))
+        BoundLType  -   boundary condition type for the left boundary
+        BoundL      -   left boundary condition (first or second derivative,
+                        depending on the BoundLType)
+        BoundRType  -   boundary condition type for the right boundary
+        BoundR      -   right boundary condition (first or second derivative,
+                        depending on the BoundRType)
+
+    OUTPUT PARAMETERS:
+        D1          -   S' values at X[]
+        D2          -   S'' values at X[]
+
+    ORDER OF POINTS
+
+    Subroutine automatically sorts points, so caller may pass unsorted array.
+    Derivative values are correctly reordered on return, so  D[I]  is  always
+    equal to S'(X[I]) independently of points order.
+
+    SETTING BOUNDARY VALUES:
+
+    The BoundLType/BoundRType parameters can have the following values:
+        * -1, which corresonds to the periodic (cyclic) boundary conditions.
+              In this case:
+              * both BoundLType and BoundRType must be equal to -1.
+              * BoundL/BoundR are ignored
+              * Y[last] is ignored (it is assumed to be equal to Y[first]).
+        *  0, which  corresponds  to  the  parabolically   terminated  spline
+              (BoundL and/or BoundR are ignored).
+        *  1, which corresponds to the first derivative boundary condition
+        *  2, which corresponds to the second derivative boundary condition
+        *  by default, BoundType=0 is used
+
+    PROBLEMS WITH PERIODIC BOUNDARY CONDITIONS:
+
+    Problems with periodic boundary conditions have Y[first_point]=Y[last_point].
+    However, this subroutine doesn't require you to specify equal  values  for
+    the first and last points - it automatically forces them  to  be  equal by
+    copying  Y[first_point]  (corresponds  to the leftmost,  minimal  X[])  to
+    Y[last_point]. However it is recommended to pass consistent values of Y[],
+    i.e. to make Y[first_point]=Y[last_point].
+
+      -- ALGLIB PROJECT --
+         Copyright 03.09.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void spline1dgriddiff2cubic(double[] x, double[] y, int n, int boundltype, double boundl, int boundrtype, double boundr, out double[] d1, out double[] d2)
+    {
+        d1 = new double[0];
+        d2 = new double[0];
+        spline1d.spline1dgriddiff2cubic(x, y, n, boundltype, boundl, boundrtype, boundr, ref d1, ref d2);
+        return;
+    }
+    public static void spline1dgriddiff2cubic(double[] x, double[] y, out double[] d1, out double[] d2)
+    {
+        int n;
+        int boundltype;
+        double boundl;
+        int boundrtype;
+        double boundr;
+        if( (ap.len(x)!=ap.len(y)))
+            throw new alglibexception("Error while calling 'spline1dgriddiff2cubic': looks like one of arguments has wrong size");
+        d1 = new double[0];
+        d2 = new double[0];
+        n = ap.len(x);
+        boundltype = 0;
+        boundl = 0;
+        boundrtype = 0;
+        boundr = 0;
+        spline1d.spline1dgriddiff2cubic(x, y, n, boundltype, boundl, boundrtype, boundr, ref d1, ref d2);
+
+        return;
+    }
+
+    /*************************************************************************
+    This function solves following problem: given table y[] of function values
+    at old nodes x[]  and new nodes  x2[],  it calculates and returns table of
+    function values y2[] (calculated at x2[]).
+
+    This function yields same result as Spline1DBuildCubic() call followed  by
+    sequence of Spline1DDiff() calls, but it can be several times faster  when
+    called for ordered X[] and X2[].
+
+    INPUT PARAMETERS:
+        X           -   old spline nodes
+        Y           -   function values
+        X2           -  new spline nodes
+
+    OPTIONAL PARAMETERS:
+        N           -   points count:
+                        * N>=2
+                        * if given, only first N points from X/Y are used
+                        * if not given, automatically detected from X/Y sizes
+                          (len(X) must be equal to len(Y))
+        BoundLType  -   boundary condition type for the left boundary
+        BoundL      -   left boundary condition (first or second derivative,
+                        depending on the BoundLType)
+        BoundRType  -   boundary condition type for the right boundary
+        BoundR      -   right boundary condition (first or second derivative,
+                        depending on the BoundRType)
+        N2          -   new points count:
+                        * N2>=2
+                        * if given, only first N2 points from X2 are used
+                        * if not given, automatically detected from X2 size
+
+    OUTPUT PARAMETERS:
+        F2          -   function values at X2[]
+
+    ORDER OF POINTS
+
+    Subroutine automatically sorts points, so caller  may pass unsorted array.
+    Function  values  are correctly reordered on  return, so F2[I]  is  always
+    equal to S(X2[I]) independently of points order.
+
+    SETTING BOUNDARY VALUES:
+
+    The BoundLType/BoundRType parameters can have the following values:
+        * -1, which corresonds to the periodic (cyclic) boundary conditions.
+              In this case:
+              * both BoundLType and BoundRType must be equal to -1.
+              * BoundL/BoundR are ignored
+              * Y[last] is ignored (it is assumed to be equal to Y[first]).
+        *  0, which  corresponds  to  the  parabolically   terminated  spline
+              (BoundL and/or BoundR are ignored).
+        *  1, which corresponds to the first derivative boundary condition
+        *  2, which corresponds to the second derivative boundary condition
+        *  by default, BoundType=0 is used
+
+    PROBLEMS WITH PERIODIC BOUNDARY CONDITIONS:
+
+    Problems with periodic boundary conditions have Y[first_point]=Y[last_point].
+    However, this subroutine doesn't require you to specify equal  values  for
+    the first and last points - it automatically forces them  to  be  equal by
+    copying  Y[first_point]  (corresponds  to the leftmost,  minimal  X[])  to
+    Y[last_point]. However it is recommended to pass consistent values of Y[],
+    i.e. to make Y[first_point]=Y[last_point].
+
+      -- ALGLIB PROJECT --
+         Copyright 03.09.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void spline1dconvcubic(double[] x, double[] y, int n, int boundltype, double boundl, int boundrtype, double boundr, double[] x2, int n2, out double[] y2)
+    {
+        y2 = new double[0];
+        spline1d.spline1dconvcubic(x, y, n, boundltype, boundl, boundrtype, boundr, x2, n2, ref y2);
+        return;
+    }
+    public static void spline1dconvcubic(double[] x, double[] y, double[] x2, out double[] y2)
+    {
+        int n;
+        int boundltype;
+        double boundl;
+        int boundrtype;
+        double boundr;
+        int n2;
+        if( (ap.len(x)!=ap.len(y)))
+            throw new alglibexception("Error while calling 'spline1dconvcubic': looks like one of arguments has wrong size");
+        y2 = new double[0];
+        n = ap.len(x);
+        boundltype = 0;
+        boundl = 0;
+        boundrtype = 0;
+        boundr = 0;
+        n2 = ap.len(x2);
+        spline1d.spline1dconvcubic(x, y, n, boundltype, boundl, boundrtype, boundr, x2, n2, ref y2);
+
+        return;
+    }
+
+    /*************************************************************************
+    This function solves following problem: given table y[] of function values
+    at old nodes x[]  and new nodes  x2[],  it calculates and returns table of
+    function values y2[] and derivatives d2[] (calculated at x2[]).
+
+    This function yields same result as Spline1DBuildCubic() call followed  by
+    sequence of Spline1DDiff() calls, but it can be several times faster  when
+    called for ordered X[] and X2[].
+
+    INPUT PARAMETERS:
+        X           -   old spline nodes
+        Y           -   function values
+        X2           -  new spline nodes
+
+    OPTIONAL PARAMETERS:
+        N           -   points count:
+                        * N>=2
+                        * if given, only first N points from X/Y are used
+                        * if not given, automatically detected from X/Y sizes
+                          (len(X) must be equal to len(Y))
+        BoundLType  -   boundary condition type for the left boundary
+        BoundL      -   left boundary condition (first or second derivative,
+                        depending on the BoundLType)
+        BoundRType  -   boundary condition type for the right boundary
+        BoundR      -   right boundary condition (first or second derivative,
+                        depending on the BoundRType)
+        N2          -   new points count:
+                        * N2>=2
+                        * if given, only first N2 points from X2 are used
+                        * if not given, automatically detected from X2 size
+
+    OUTPUT PARAMETERS:
+        F2          -   function values at X2[]
+        D2          -   first derivatives at X2[]
+
+    ORDER OF POINTS
+
+    Subroutine automatically sorts points, so caller  may pass unsorted array.
+    Function  values  are correctly reordered on  return, so F2[I]  is  always
+    equal to S(X2[I]) independently of points order.
+
+    SETTING BOUNDARY VALUES:
+
+    The BoundLType/BoundRType parameters can have the following values:
+        * -1, which corresonds to the periodic (cyclic) boundary conditions.
+              In this case:
+              * both BoundLType and BoundRType must be equal to -1.
+              * BoundL/BoundR are ignored
+              * Y[last] is ignored (it is assumed to be equal to Y[first]).
+        *  0, which  corresponds  to  the  parabolically   terminated  spline
+              (BoundL and/or BoundR are ignored).
+        *  1, which corresponds to the first derivative boundary condition
+        *  2, which corresponds to the second derivative boundary condition
+        *  by default, BoundType=0 is used
+
+    PROBLEMS WITH PERIODIC BOUNDARY CONDITIONS:
+
+    Problems with periodic boundary conditions have Y[first_point]=Y[last_point].
+    However, this subroutine doesn't require you to specify equal  values  for
+    the first and last points - it automatically forces them  to  be  equal by
+    copying  Y[first_point]  (corresponds  to the leftmost,  minimal  X[])  to
+    Y[last_point]. However it is recommended to pass consistent values of Y[],
+    i.e. to make Y[first_point]=Y[last_point].
+
+      -- ALGLIB PROJECT --
+         Copyright 03.09.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void spline1dconvdiffcubic(double[] x, double[] y, int n, int boundltype, double boundl, int boundrtype, double boundr, double[] x2, int n2, out double[] y2, out double[] d2)
+    {
+        y2 = new double[0];
+        d2 = new double[0];
+        spline1d.spline1dconvdiffcubic(x, y, n, boundltype, boundl, boundrtype, boundr, x2, n2, ref y2, ref d2);
+        return;
+    }
+    public static void spline1dconvdiffcubic(double[] x, double[] y, double[] x2, out double[] y2, out double[] d2)
+    {
+        int n;
+        int boundltype;
+        double boundl;
+        int boundrtype;
+        double boundr;
+        int n2;
+        if( (ap.len(x)!=ap.len(y)))
+            throw new alglibexception("Error while calling 'spline1dconvdiffcubic': looks like one of arguments has wrong size");
+        y2 = new double[0];
+        d2 = new double[0];
+        n = ap.len(x);
+        boundltype = 0;
+        boundl = 0;
+        boundrtype = 0;
+        boundr = 0;
+        n2 = ap.len(x2);
+        spline1d.spline1dconvdiffcubic(x, y, n, boundltype, boundl, boundrtype, boundr, x2, n2, ref y2, ref d2);
+
+        return;
+    }
+
+    /*************************************************************************
+    This function solves following problem: given table y[] of function values
+    at old nodes x[]  and new nodes  x2[],  it calculates and returns table of
+    function  values  y2[],  first  and  second  derivatives  d2[]  and  dd2[]
+    (calculated at x2[]).
+
+    This function yields same result as Spline1DBuildCubic() call followed  by
+    sequence of Spline1DDiff() calls, but it can be several times faster  when
+    called for ordered X[] and X2[].
+
+    INPUT PARAMETERS:
+        X           -   old spline nodes
+        Y           -   function values
+        X2           -  new spline nodes
+
+    OPTIONAL PARAMETERS:
+        N           -   points count:
+                        * N>=2
+                        * if given, only first N points from X/Y are used
+                        * if not given, automatically detected from X/Y sizes
+                          (len(X) must be equal to len(Y))
+        BoundLType  -   boundary condition type for the left boundary
+        BoundL      -   left boundary condition (first or second derivative,
+                        depending on the BoundLType)
+        BoundRType  -   boundary condition type for the right boundary
+        BoundR      -   right boundary condition (first or second derivative,
+                        depending on the BoundRType)
+        N2          -   new points count:
+                        * N2>=2
+                        * if given, only first N2 points from X2 are used
+                        * if not given, automatically detected from X2 size
+
+    OUTPUT PARAMETERS:
+        F2          -   function values at X2[]
+        D2          -   first derivatives at X2[]
+        DD2         -   second derivatives at X2[]
+
+    ORDER OF POINTS
+
+    Subroutine automatically sorts points, so caller  may pass unsorted array.
+    Function  values  are correctly reordered on  return, so F2[I]  is  always
+    equal to S(X2[I]) independently of points order.
+
+    SETTING BOUNDARY VALUES:
+
+    The BoundLType/BoundRType parameters can have the following values:
+        * -1, which corresonds to the periodic (cyclic) boundary conditions.
+              In this case:
+              * both BoundLType and BoundRType must be equal to -1.
+              * BoundL/BoundR are ignored
+              * Y[last] is ignored (it is assumed to be equal to Y[first]).
+        *  0, which  corresponds  to  the  parabolically   terminated  spline
+              (BoundL and/or BoundR are ignored).
+        *  1, which corresponds to the first derivative boundary condition
+        *  2, which corresponds to the second derivative boundary condition
+        *  by default, BoundType=0 is used
+
+    PROBLEMS WITH PERIODIC BOUNDARY CONDITIONS:
+
+    Problems with periodic boundary conditions have Y[first_point]=Y[last_point].
+    However, this subroutine doesn't require you to specify equal  values  for
+    the first and last points - it automatically forces them  to  be  equal by
+    copying  Y[first_point]  (corresponds  to the leftmost,  minimal  X[])  to
+    Y[last_point]. However it is recommended to pass consistent values of Y[],
+    i.e. to make Y[first_point]=Y[last_point].
+
+      -- ALGLIB PROJECT --
+         Copyright 03.09.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void spline1dconvdiff2cubic(double[] x, double[] y, int n, int boundltype, double boundl, int boundrtype, double boundr, double[] x2, int n2, out double[] y2, out double[] d2, out double[] dd2)
+    {
+        y2 = new double[0];
+        d2 = new double[0];
+        dd2 = new double[0];
+        spline1d.spline1dconvdiff2cubic(x, y, n, boundltype, boundl, boundrtype, boundr, x2, n2, ref y2, ref d2, ref dd2);
+        return;
+    }
+    public static void spline1dconvdiff2cubic(double[] x, double[] y, double[] x2, out double[] y2, out double[] d2, out double[] dd2)
+    {
+        int n;
+        int boundltype;
+        double boundl;
+        int boundrtype;
+        double boundr;
+        int n2;
+        if( (ap.len(x)!=ap.len(y)))
+            throw new alglibexception("Error while calling 'spline1dconvdiff2cubic': looks like one of arguments has wrong size");
+        y2 = new double[0];
+        d2 = new double[0];
+        dd2 = new double[0];
+        n = ap.len(x);
+        boundltype = 0;
+        boundl = 0;
+        boundrtype = 0;
+        boundr = 0;
+        n2 = ap.len(x2);
+        spline1d.spline1dconvdiff2cubic(x, y, n, boundltype, boundl, boundrtype, boundr, x2, n2, ref y2, ref d2, ref dd2);
+
+        return;
+    }
+
+    /*************************************************************************
+    This subroutine builds Catmull-Rom spline interpolant.
+
+    INPUT PARAMETERS:
+        X           -   spline nodes, array[0..N-1].
+        Y           -   function values, array[0..N-1].
+
+    OPTIONAL PARAMETERS:
+        N           -   points count:
+                        * N>=2
+                        * if given, only first N points are used to build spline
+                        * if not given, automatically detected from X/Y sizes
+                          (len(X) must be equal to len(Y))
+        BoundType   -   boundary condition type:
+                        * -1 for periodic boundary condition
+                        *  0 for parabolically terminated spline (default)
+        Tension     -   tension parameter:
+                        * tension=0   corresponds to classic Catmull-Rom spline (default)
+                        * 0<tension<1 corresponds to more general form - cardinal spline
+
+    OUTPUT PARAMETERS:
+        C           -   spline interpolant
+
+
+    ORDER OF POINTS
+
+    Subroutine automatically sorts points, so caller may pass unsorted array.
+
+    PROBLEMS WITH PERIODIC BOUNDARY CONDITIONS:
+
+    Problems with periodic boundary conditions have Y[first_point]=Y[last_point].
+    However, this subroutine doesn't require you to specify equal  values  for
+    the first and last points - it automatically forces them  to  be  equal by
+    copying  Y[first_point]  (corresponds  to the leftmost,  minimal  X[])  to
+    Y[last_point]. However it is recommended to pass consistent values of Y[],
+    i.e. to make Y[first_point]=Y[last_point].
+
+      -- ALGLIB PROJECT --
+         Copyright 23.06.2007 by Bochkanov Sergey
+    *************************************************************************/
+    public static void spline1dbuildcatmullrom(double[] x, double[] y, int n, int boundtype, double tension, out spline1dinterpolant c)
+    {
+        c = new spline1dinterpolant();
+        spline1d.spline1dbuildcatmullrom(x, y, n, boundtype, tension, c.innerobj);
+        return;
+    }
+    public static void spline1dbuildcatmullrom(double[] x, double[] y, out spline1dinterpolant c)
+    {
+        int n;
+        int boundtype;
+        double tension;
+        if( (ap.len(x)!=ap.len(y)))
+            throw new alglibexception("Error while calling 'spline1dbuildcatmullrom': looks like one of arguments has wrong size");
+        c = new spline1dinterpolant();
+        n = ap.len(x);
+        boundtype = 0;
+        tension = 0;
+        spline1d.spline1dbuildcatmullrom(x, y, n, boundtype, tension, c.innerobj);
+
+        return;
+    }
+
+    /*************************************************************************
+    This subroutine builds Hermite spline interpolant.
+
+    INPUT PARAMETERS:
+        X           -   spline nodes, array[0..N-1]
+        Y           -   function values, array[0..N-1]
+        D           -   derivatives, array[0..N-1]
+        N           -   points count (optional):
+                        * N>=2
+                        * if given, only first N points are used to build spline
+                        * if not given, automatically detected from X/Y sizes
+                          (len(X) must be equal to len(Y))
+
+    OUTPUT PARAMETERS:
+        C           -   spline interpolant.
+
+
+    ORDER OF POINTS
+
+    Subroutine automatically sorts points, so caller may pass unsorted array.
+
+      -- ALGLIB PROJECT --
+         Copyright 23.06.2007 by Bochkanov Sergey
+    *************************************************************************/
+    public static void spline1dbuildhermite(double[] x, double[] y, double[] d, int n, out spline1dinterpolant c)
+    {
+        c = new spline1dinterpolant();
+        spline1d.spline1dbuildhermite(x, y, d, n, c.innerobj);
+        return;
+    }
+    public static void spline1dbuildhermite(double[] x, double[] y, double[] d, out spline1dinterpolant c)
+    {
+        int n;
+        if( (ap.len(x)!=ap.len(y)) || (ap.len(x)!=ap.len(d)))
+            throw new alglibexception("Error while calling 'spline1dbuildhermite': looks like one of arguments has wrong size");
+        c = new spline1dinterpolant();
+        n = ap.len(x);
+        spline1d.spline1dbuildhermite(x, y, d, n, c.innerobj);
+
+        return;
+    }
+
+    /*************************************************************************
+    This subroutine builds Akima spline interpolant
+
+    INPUT PARAMETERS:
+        X           -   spline nodes, array[0..N-1]
+        Y           -   function values, array[0..N-1]
+        N           -   points count (optional):
+                        * N>=5
+                        * if given, only first N points are used to build spline
+                        * if not given, automatically detected from X/Y sizes
+                          (len(X) must be equal to len(Y))
+
+    OUTPUT PARAMETERS:
+        C           -   spline interpolant
+
+
+    ORDER OF POINTS
+
+    Subroutine automatically sorts points, so caller may pass unsorted array.
+
+      -- ALGLIB PROJECT --
+         Copyright 24.06.2007 by Bochkanov Sergey
+    *************************************************************************/
+    public static void spline1dbuildakima(double[] x, double[] y, int n, out spline1dinterpolant c)
+    {
+        c = new spline1dinterpolant();
+        spline1d.spline1dbuildakima(x, y, n, c.innerobj);
+        return;
+    }
+    public static void spline1dbuildakima(double[] x, double[] y, out spline1dinterpolant c)
+    {
+        int n;
+        if( (ap.len(x)!=ap.len(y)))
+            throw new alglibexception("Error while calling 'spline1dbuildakima': looks like one of arguments has wrong size");
+        c = new spline1dinterpolant();
+        n = ap.len(x);
+        spline1d.spline1dbuildakima(x, y, n, c.innerobj);
+
+        return;
+    }
+
+    /*************************************************************************
+    This subroutine calculates the value of the spline at the given point X.
+
+    INPUT PARAMETERS:
+        C   -   spline interpolant
+        X   -   point
+
+    Result:
+        S(x)
+
+      -- ALGLIB PROJECT --
+         Copyright 23.06.2007 by Bochkanov Sergey
+    *************************************************************************/
+    public static double spline1dcalc(spline1dinterpolant c, double x)
+    {
+
+        double result = spline1d.spline1dcalc(c.innerobj, x);
+        return result;
+    }
+
+    /*************************************************************************
+    This subroutine differentiates the spline.
+
+    INPUT PARAMETERS:
+        C   -   spline interpolant.
+        X   -   point
+
+    Result:
+        S   -   S(x)
+        DS  -   S'(x)
+        D2S -   S''(x)
+
+      -- ALGLIB PROJECT --
+         Copyright 24.06.2007 by Bochkanov Sergey
+    *************************************************************************/
+    public static void spline1ddiff(spline1dinterpolant c, double x, out double s, out double ds, out double d2s)
+    {
+        s = 0;
+        ds = 0;
+        d2s = 0;
+        spline1d.spline1ddiff(c.innerobj, x, ref s, ref ds, ref d2s);
+        return;
+    }
+
+    /*************************************************************************
+    This subroutine unpacks the spline into the coefficients table.
+
+    INPUT PARAMETERS:
+        C   -   spline interpolant.
+        X   -   point
+
+    Result:
+        Tbl -   coefficients table, unpacked format, array[0..N-2, 0..5].
+                For I = 0...N-2:
+                    Tbl[I,0] = X[i]
+                    Tbl[I,1] = X[i+1]
+                    Tbl[I,2] = C0
+                    Tbl[I,3] = C1
+                    Tbl[I,4] = C2
+                    Tbl[I,5] = C3
+                On [x[i], x[i+1]] spline is equals to:
+                    S(x) = C0 + C1*t + C2*t^2 + C3*t^3
+                    t = x-x[i]
+
+      -- ALGLIB PROJECT --
+         Copyright 29.06.2007 by Bochkanov Sergey
+    *************************************************************************/
+    public static void spline1dunpack(spline1dinterpolant c, out int n, out double[,] tbl)
+    {
+        n = 0;
+        tbl = new double[0,0];
+        spline1d.spline1dunpack(c.innerobj, ref n, ref tbl);
+        return;
+    }
+
+    /*************************************************************************
+    This subroutine performs linear transformation of the spline argument.
+
+    INPUT PARAMETERS:
+        C   -   spline interpolant.
+        A, B-   transformation coefficients: x = A*t + B
+    Result:
+        C   -   transformed spline
+
+      -- ALGLIB PROJECT --
+         Copyright 30.06.2007 by Bochkanov Sergey
+    *************************************************************************/
+    public static void spline1dlintransx(spline1dinterpolant c, double a, double b)
+    {
+
+        spline1d.spline1dlintransx(c.innerobj, a, b);
+        return;
+    }
+
+    /*************************************************************************
+    This subroutine performs linear transformation of the spline.
+
+    INPUT PARAMETERS:
+        C   -   spline interpolant.
+        A, B-   transformation coefficients: S2(x) = A*S(x) + B
+    Result:
+        C   -   transformed spline
+
+      -- ALGLIB PROJECT --
+         Copyright 30.06.2007 by Bochkanov Sergey
+    *************************************************************************/
+    public static void spline1dlintransy(spline1dinterpolant c, double a, double b)
+    {
+
+        spline1d.spline1dlintransy(c.innerobj, a, b);
+        return;
+    }
+
+    /*************************************************************************
+    This subroutine integrates the spline.
+
+    INPUT PARAMETERS:
+        C   -   spline interpolant.
+        X   -   right bound of the integration interval [a, x],
+                here 'a' denotes min(x[])
+    Result:
+        integral(S(t)dt,a,x)
+
+      -- ALGLIB PROJECT --
+         Copyright 23.06.2007 by Bochkanov Sergey
+    *************************************************************************/
+    public static double spline1dintegrate(spline1dinterpolant c, double x)
+    {
+
+        double result = spline1d.spline1dintegrate(c.innerobj, x);
+        return result;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    Polynomial fitting report:
+        TaskRCond       reciprocal of task's condition number
+        RMSError        RMS error
+        AvgError        average error
+        AvgRelError     average relative error (for non-zero Y[I])
+        MaxError        maximum error
+    *************************************************************************/
+    public class polynomialfitreport
+    {
+        //
+        // Public declarations
+        //
+        public double taskrcond { get { return _innerobj.taskrcond; } set { _innerobj.taskrcond = value; } }
+        public double rmserror { get { return _innerobj.rmserror; } set { _innerobj.rmserror = value; } }
+        public double avgerror { get { return _innerobj.avgerror; } set { _innerobj.avgerror = value; } }
+        public double avgrelerror { get { return _innerobj.avgrelerror; } set { _innerobj.avgrelerror = value; } }
+        public double maxerror { get { return _innerobj.maxerror; } set { _innerobj.maxerror = value; } }
+
+        public polynomialfitreport()
+        {
+            _innerobj = new lsfit.polynomialfitreport();
+        }
+
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private lsfit.polynomialfitreport _innerobj;
+        public lsfit.polynomialfitreport innerobj { get { return _innerobj; } }
+        public polynomialfitreport(lsfit.polynomialfitreport obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+
+    /*************************************************************************
+    Barycentric fitting report:
+        RMSError        RMS error
+        AvgError        average error
+        AvgRelError     average relative error (for non-zero Y[I])
+        MaxError        maximum error
+        TaskRCond       reciprocal of task's condition number
+    *************************************************************************/
+    public class barycentricfitreport
+    {
+        //
+        // Public declarations
+        //
+        public double taskrcond { get { return _innerobj.taskrcond; } set { _innerobj.taskrcond = value; } }
+        public int dbest { get { return _innerobj.dbest; } set { _innerobj.dbest = value; } }
+        public double rmserror { get { return _innerobj.rmserror; } set { _innerobj.rmserror = value; } }
+        public double avgerror { get { return _innerobj.avgerror; } set { _innerobj.avgerror = value; } }
+        public double avgrelerror { get { return _innerobj.avgrelerror; } set { _innerobj.avgrelerror = value; } }
+        public double maxerror { get { return _innerobj.maxerror; } set { _innerobj.maxerror = value; } }
+
+        public barycentricfitreport()
+        {
+            _innerobj = new lsfit.barycentricfitreport();
+        }
+
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private lsfit.barycentricfitreport _innerobj;
+        public lsfit.barycentricfitreport innerobj { get { return _innerobj; } }
+        public barycentricfitreport(lsfit.barycentricfitreport obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+
+    /*************************************************************************
+    Spline fitting report:
+        RMSError        RMS error
+        AvgError        average error
+        AvgRelError     average relative error (for non-zero Y[I])
+        MaxError        maximum error
+
+    Fields  below are  filled  by   obsolete    functions   (Spline1DFitCubic,
+    Spline1DFitHermite). Modern fitting functions do NOT fill these fields:
+        TaskRCond       reciprocal of task's condition number
+    *************************************************************************/
+    public class spline1dfitreport
+    {
+        //
+        // Public declarations
+        //
+        public double taskrcond { get { return _innerobj.taskrcond; } set { _innerobj.taskrcond = value; } }
+        public double rmserror { get { return _innerobj.rmserror; } set { _innerobj.rmserror = value; } }
+        public double avgerror { get { return _innerobj.avgerror; } set { _innerobj.avgerror = value; } }
+        public double avgrelerror { get { return _innerobj.avgrelerror; } set { _innerobj.avgrelerror = value; } }
+        public double maxerror { get { return _innerobj.maxerror; } set { _innerobj.maxerror = value; } }
+
+        public spline1dfitreport()
+        {
+            _innerobj = new lsfit.spline1dfitreport();
+        }
+
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private lsfit.spline1dfitreport _innerobj;
+        public lsfit.spline1dfitreport innerobj { get { return _innerobj; } }
+        public spline1dfitreport(lsfit.spline1dfitreport obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+
+    /*************************************************************************
+    Least squares fitting report:
+        TaskRCond       reciprocal of task's condition number
+        IterationsCount number of internal iterations
+
+        RMSError        RMS error
+        AvgError        average error
+        AvgRelError     average relative error (for non-zero Y[I])
+        MaxError        maximum error
+
+        WRMSError       weighted RMS error
+    *************************************************************************/
+    public class lsfitreport
+    {
+        //
+        // Public declarations
+        //
+        public double taskrcond { get { return _innerobj.taskrcond; } set { _innerobj.taskrcond = value; } }
+        public int iterationscount { get { return _innerobj.iterationscount; } set { _innerobj.iterationscount = value; } }
+        public double rmserror { get { return _innerobj.rmserror; } set { _innerobj.rmserror = value; } }
+        public double avgerror { get { return _innerobj.avgerror; } set { _innerobj.avgerror = value; } }
+        public double avgrelerror { get { return _innerobj.avgrelerror; } set { _innerobj.avgrelerror = value; } }
+        public double maxerror { get { return _innerobj.maxerror; } set { _innerobj.maxerror = value; } }
+        public double wrmserror { get { return _innerobj.wrmserror; } set { _innerobj.wrmserror = value; } }
+
+        public lsfitreport()
+        {
+            _innerobj = new lsfit.lsfitreport();
+        }
+
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private lsfit.lsfitreport _innerobj;
+        public lsfit.lsfitreport innerobj { get { return _innerobj; } }
+        public lsfitreport(lsfit.lsfitreport obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+
+    /*************************************************************************
+    Nonlinear fitter.
+
+    You should use ALGLIB functions to work with fitter.
+    Never try to access its fields directly!
+    *************************************************************************/
+    public class lsfitstate
+    {
+        //
+        // Public declarations
+        //
+        public bool needf { get { return _innerobj.needf; } set { _innerobj.needf = value; } }
+        public bool needfg { get { return _innerobj.needfg; } set { _innerobj.needfg = value; } }
+        public bool needfgh { get { return _innerobj.needfgh; } set { _innerobj.needfgh = value; } }
+        public bool xupdated { get { return _innerobj.xupdated; } set { _innerobj.xupdated = value; } }
+        public double[] c { get { return _innerobj.c; } }
+        public double f { get { return _innerobj.f; } set { _innerobj.f = value; } }
+        public double[] g { get { return _innerobj.g; } }
+        public double[,] h { get { return _innerobj.h; } }
+        public double[] x { get { return _innerobj.x; } }
+
+        public lsfitstate()
+        {
+            _innerobj = new lsfit.lsfitstate();
+        }
+
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private lsfit.lsfitstate _innerobj;
+        public lsfit.lsfitstate innerobj { get { return _innerobj; } }
+        public lsfitstate(lsfit.lsfitstate obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+    /*************************************************************************
+    Fitting by polynomials in barycentric form. This function provides  simple
+    unterface for unconstrained unweighted fitting. See  PolynomialFitWC()  if
+    you need constrained fitting.
+
+    Task is linear, so linear least squares solver is used. Complexity of this
+    computational scheme is O(N*M^2), mostly dominated by least squares solver
+
+    SEE ALSO:
+        PolynomialFitWC()
+
+    INPUT PARAMETERS:
+        X   -   points, array[0..N-1].
+        Y   -   function values, array[0..N-1].
+        N   -   number of points, N>0
+                * if given, only leading N elements of X/Y are used
+                * if not given, automatically determined from sizes of X/Y
+        M   -   number of basis functions (= polynomial_degree + 1), M>=1
+
+    OUTPUT PARAMETERS:
+        Info-   same format as in LSFitLinearW() subroutine:
+                * Info>0    task is solved
+                * Info<=0   an error occured:
+                            -4 means inconvergence of internal SVD
+        P   -   interpolant in barycentric form.
+        Rep -   report, same format as in LSFitLinearW() subroutine.
+                Following fields are set:
+                * RMSError      rms error on the (X,Y).
+                * AvgError      average error on the (X,Y).
+                * AvgRelError   average relative error on the non-zero Y
+                * MaxError      maximum error
+                                NON-WEIGHTED ERRORS ARE CALCULATED
+
+    NOTES:
+        you can convert P from barycentric form  to  the  power  or  Chebyshev
+        basis with PolynomialBar2Pow() or PolynomialBar2Cheb() functions  from
+        POLINT subpackage.
+
+      -- ALGLIB PROJECT --
+         Copyright 10.12.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void polynomialfit(double[] x, double[] y, int n, int m, out int info, out barycentricinterpolant p, out polynomialfitreport rep)
+    {
+        info = 0;
+        p = new barycentricinterpolant();
+        rep = new polynomialfitreport();
+        lsfit.polynomialfit(x, y, n, m, ref info, p.innerobj, rep.innerobj);
+        return;
+    }
+    public static void polynomialfit(double[] x, double[] y, int m, out int info, out barycentricinterpolant p, out polynomialfitreport rep)
+    {
+        int n;
+        if( (ap.len(x)!=ap.len(y)))
+            throw new alglibexception("Error while calling 'polynomialfit': looks like one of arguments has wrong size");
+        info = 0;
+        p = new barycentricinterpolant();
+        rep = new polynomialfitreport();
+        n = ap.len(x);
+        lsfit.polynomialfit(x, y, n, m, ref info, p.innerobj, rep.innerobj);
+
+        return;
+    }
+
+    /*************************************************************************
+    Weighted  fitting by polynomials in barycentric form, with constraints  on
+    function values or first derivatives.
+
+    Small regularizing term is used when solving constrained tasks (to improve
+    stability).
+
+    Task is linear, so linear least squares solver is used. Complexity of this
+    computational scheme is O(N*M^2), mostly dominated by least squares solver
+
+    SEE ALSO:
+        PolynomialFit()
+
+    INPUT PARAMETERS:
+        X   -   points, array[0..N-1].
+        Y   -   function values, array[0..N-1].
+        W   -   weights, array[0..N-1]
+                Each summand in square  sum  of  approximation deviations from
+                given  values  is  multiplied  by  the square of corresponding
+                weight. Fill it by 1's if you don't  want  to  solve  weighted
+                task.
+        N   -   number of points, N>0.
+                * if given, only leading N elements of X/Y/W are used
+                * if not given, automatically determined from sizes of X/Y/W
+        XC  -   points where polynomial values/derivatives are constrained,
+                array[0..K-1].
+        YC  -   values of constraints, array[0..K-1]
+        DC  -   array[0..K-1], types of constraints:
+                * DC[i]=0   means that P(XC[i])=YC[i]
+                * DC[i]=1   means that P'(XC[i])=YC[i]
+                SEE BELOW FOR IMPORTANT INFORMATION ON CONSTRAINTS
+        K   -   number of constraints, 0<=K<M.
+                K=0 means no constraints (XC/YC/DC are not used in such cases)
+        M   -   number of basis functions (= polynomial_degree + 1), M>=1
+
+    OUTPUT PARAMETERS:
+        Info-   same format as in LSFitLinearW() subroutine:
+                * Info>0    task is solved
+                * Info<=0   an error occured:
+                            -4 means inconvergence of internal SVD
+                            -3 means inconsistent constraints
+        P   -   interpolant in barycentric form.
+        Rep -   report, same format as in LSFitLinearW() subroutine.
+                Following fields are set:
+                * RMSError      rms error on the (X,Y).
+                * AvgError      average error on the (X,Y).
+                * AvgRelError   average relative error on the non-zero Y
+                * MaxError      maximum error
+                                NON-WEIGHTED ERRORS ARE CALCULATED
+
+    IMPORTANT:
+        this subroitine doesn't calculate task's condition number for K<>0.
+
+    NOTES:
+        you can convert P from barycentric form  to  the  power  or  Chebyshev
+        basis with PolynomialBar2Pow() or PolynomialBar2Cheb() functions  from
+        POLINT subpackage.
+
+    SETTING CONSTRAINTS - DANGERS AND OPPORTUNITIES:
+
+    Setting constraints can lead  to undesired  results,  like ill-conditioned
+    behavior, or inconsistency being detected. From the other side,  it allows
+    us to improve quality of the fit. Here we summarize  our  experience  with
+    constrained regression splines:
+    * even simple constraints can be inconsistent, see  Wikipedia  article  on
+      this subject: http://en.wikipedia.org/wiki/Birkhoff_interpolation
+    * the  greater  is  M (given  fixed  constraints),  the  more chances that
+      constraints will be consistent
+    * in the general case, consistency of constraints is NOT GUARANTEED.
+    * in the one special cases, however, we can  guarantee  consistency.  This
+      case  is:  M>1  and constraints on the function values (NOT DERIVATIVES)
+
+    Our final recommendation is to use constraints  WHEN  AND  ONLY  when  you
+    can't solve your task without them. Anything beyond  special  cases  given
+    above is not guaranteed and may result in inconsistency.
+
+      -- ALGLIB PROJECT --
+         Copyright 10.12.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void polynomialfitwc(double[] x, double[] y, double[] w, int n, double[] xc, double[] yc, int[] dc, int k, int m, out int info, out barycentricinterpolant p, out polynomialfitreport rep)
+    {
+        info = 0;
+        p = new barycentricinterpolant();
+        rep = new polynomialfitreport();
+        lsfit.polynomialfitwc(x, y, w, n, xc, yc, dc, k, m, ref info, p.innerobj, rep.innerobj);
+        return;
+    }
+    public static void polynomialfitwc(double[] x, double[] y, double[] w, double[] xc, double[] yc, int[] dc, int m, out int info, out barycentricinterpolant p, out polynomialfitreport rep)
+    {
+        int n;
+        int k;
+        if( (ap.len(x)!=ap.len(y)) || (ap.len(x)!=ap.len(w)))
+            throw new alglibexception("Error while calling 'polynomialfitwc': looks like one of arguments has wrong size");
+        if( (ap.len(xc)!=ap.len(yc)) || (ap.len(xc)!=ap.len(dc)))
+            throw new alglibexception("Error while calling 'polynomialfitwc': looks like one of arguments has wrong size");
+        info = 0;
+        p = new barycentricinterpolant();
+        rep = new polynomialfitreport();
+        n = ap.len(x);
+        k = ap.len(xc);
+        lsfit.polynomialfitwc(x, y, w, n, xc, yc, dc, k, m, ref info, p.innerobj, rep.innerobj);
+
+        return;
+    }
+
+    /*************************************************************************
+    Weghted rational least  squares  fitting  using  Floater-Hormann  rational
+    functions  with  optimal  D  chosen  from  [0,9],  with  constraints   and
+    individual weights.
+
+    Equidistant  grid  with M node on [min(x),max(x)]  is  used to build basis
+    functions. Different values of D are tried, optimal D (least WEIGHTED root
+    mean square error) is chosen.  Task  is  linear,  so  linear least squares
+    solver  is  used.  Complexity  of  this  computational  scheme is O(N*M^2)
+    (mostly dominated by the least squares solver).
+
+    SEE ALSO
+    * BarycentricFitFloaterHormann(), "lightweight" fitting without invididual
+      weights and constraints.
+
+    INPUT PARAMETERS:
+        X   -   points, array[0..N-1].
+        Y   -   function values, array[0..N-1].
+        W   -   weights, array[0..N-1]
+                Each summand in square  sum  of  approximation deviations from
+                given  values  is  multiplied  by  the square of corresponding
+                weight. Fill it by 1's if you don't  want  to  solve  weighted
+                task.
+        N   -   number of points, N>0.
+        XC  -   points where function values/derivatives are constrained,
+                array[0..K-1].
+        YC  -   values of constraints, array[0..K-1]
+        DC  -   array[0..K-1], types of constraints:
+                * DC[i]=0   means that S(XC[i])=YC[i]
+                * DC[i]=1   means that S'(XC[i])=YC[i]
+                SEE BELOW FOR IMPORTANT INFORMATION ON CONSTRAINTS
+        K   -   number of constraints, 0<=K<M.
+                K=0 means no constraints (XC/YC/DC are not used in such cases)
+        M   -   number of basis functions ( = number_of_nodes), M>=2.
+
+    OUTPUT PARAMETERS:
+        Info-   same format as in LSFitLinearWC() subroutine.
+                * Info>0    task is solved
+                * Info<=0   an error occured:
+                            -4 means inconvergence of internal SVD
+                            -3 means inconsistent constraints
+                            -1 means another errors in parameters passed
+                               (N<=0, for example)
+        B   -   barycentric interpolant.
+        Rep -   report, same format as in LSFitLinearWC() subroutine.
+                Following fields are set:
+                * DBest         best value of the D parameter
+                * RMSError      rms error on the (X,Y).
+                * AvgError      average error on the (X,Y).
+                * AvgRelError   average relative error on the non-zero Y
+                * MaxError      maximum error
+                                NON-WEIGHTED ERRORS ARE CALCULATED
+
+    IMPORTANT:
+        this subroutine doesn't calculate task's condition number for K<>0.
+
+    SETTING CONSTRAINTS - DANGERS AND OPPORTUNITIES:
+
+    Setting constraints can lead  to undesired  results,  like ill-conditioned
+    behavior, or inconsistency being detected. From the other side,  it allows
+    us to improve quality of the fit. Here we summarize  our  experience  with
+    constrained barycentric interpolants:
+    * excessive  constraints  can  be  inconsistent.   Floater-Hormann   basis
+      functions aren't as flexible as splines (although they are very smooth).
+    * the more evenly constraints are spread across [min(x),max(x)],  the more
+      chances that they will be consistent
+    * the  greater  is  M (given  fixed  constraints),  the  more chances that
+      constraints will be consistent
+    * in the general case, consistency of constraints IS NOT GUARANTEED.
+    * in the several special cases, however, we CAN guarantee consistency.
+    * one of this cases is constraints on the function  VALUES at the interval
+      boundaries. Note that consustency of the  constraints  on  the  function
+      DERIVATIVES is NOT guaranteed (you can use in such cases  cubic  splines
+      which are more flexible).
+    * another  special  case  is ONE constraint on the function value (OR, but
+      not AND, derivative) anywhere in the interval
+
+    Our final recommendation is to use constraints  WHEN  AND  ONLY  WHEN  you
+    can't solve your task without them. Anything beyond  special  cases  given
+    above is not guaranteed and may result in inconsistency.
+
+      -- ALGLIB PROJECT --
+         Copyright 18.08.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void barycentricfitfloaterhormannwc(double[] x, double[] y, double[] w, int n, double[] xc, double[] yc, int[] dc, int k, int m, out int info, out barycentricinterpolant b, out barycentricfitreport rep)
+    {
+        info = 0;
+        b = new barycentricinterpolant();
+        rep = new barycentricfitreport();
+        lsfit.barycentricfitfloaterhormannwc(x, y, w, n, xc, yc, dc, k, m, ref info, b.innerobj, rep.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    Rational least squares fitting using  Floater-Hormann  rational  functions
+    with optimal D chosen from [0,9].
+
+    Equidistant  grid  with M node on [min(x),max(x)]  is  used to build basis
+    functions. Different values of D are tried, optimal  D  (least  root  mean
+    square error) is chosen.  Task  is  linear, so linear least squares solver
+    is used. Complexity  of  this  computational  scheme is  O(N*M^2)  (mostly
+    dominated by the least squares solver).
+
+    INPUT PARAMETERS:
+        X   -   points, array[0..N-1].
+        Y   -   function values, array[0..N-1].
+        N   -   number of points, N>0.
+        M   -   number of basis functions ( = number_of_nodes), M>=2.
+
+    OUTPUT PARAMETERS:
+        Info-   same format as in LSFitLinearWC() subroutine.
+                * Info>0    task is solved
+                * Info<=0   an error occured:
+                            -4 means inconvergence of internal SVD
+                            -3 means inconsistent constraints
+        B   -   barycentric interpolant.
+        Rep -   report, same format as in LSFitLinearWC() subroutine.
+                Following fields are set:
+                * DBest         best value of the D parameter
+                * RMSError      rms error on the (X,Y).
+                * AvgError      average error on the (X,Y).
+                * AvgRelError   average relative error on the non-zero Y
+                * MaxError      maximum error
+                                NON-WEIGHTED ERRORS ARE CALCULATED
+
+      -- ALGLIB PROJECT --
+         Copyright 18.08.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void barycentricfitfloaterhormann(double[] x, double[] y, int n, int m, out int info, out barycentricinterpolant b, out barycentricfitreport rep)
+    {
+        info = 0;
+        b = new barycentricinterpolant();
+        rep = new barycentricfitreport();
+        lsfit.barycentricfitfloaterhormann(x, y, n, m, ref info, b.innerobj, rep.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    Rational least squares fitting using  Floater-Hormann  rational  functions
+    with optimal D chosen from [0,9].
+
+    Equidistant  grid  with M node on [min(x),max(x)]  is  used to build basis
+    functions. Different values of D are tried, optimal  D  (least  root  mean
+    square error) is chosen.  Task  is  linear, so linear least squares solver
+    is used. Complexity  of  this  computational  scheme is  O(N*M^2)  (mostly
+    dominated by the least squares solver).
+
+    INPUT PARAMETERS:
+        X   -   points, array[0..N-1].
+        Y   -   function values, array[0..N-1].
+        N   -   number of points, N>0.
+        M   -   number of basis functions ( = number_of_nodes), M>=2.
+
+    OUTPUT PARAMETERS:
+        Info-   same format as in LSFitLinearWC() subroutine.
+                * Info>0    task is solved
+                * Info<=0   an error occured:
+                            -4 means inconvergence of internal SVD
+                            -3 means inconsistent constraints
+        B   -   barycentric interpolant.
+        Rep -   report, same format as in LSFitLinearWC() subroutine.
+                Following fields are set:
+                * DBest         best value of the D parameter
+                * RMSError      rms error on the (X,Y).
+                * AvgError      average error on the (X,Y).
+                * AvgRelError   average relative error on the non-zero Y
+                * MaxError      maximum error
+                                NON-WEIGHTED ERRORS ARE CALCULATED
+
+      -- ALGLIB PROJECT --
+         Copyright 18.08.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void spline1dfitpenalized(double[] x, double[] y, int n, int m, double rho, out int info, out spline1dinterpolant s, out spline1dfitreport rep)
+    {
+        info = 0;
+        s = new spline1dinterpolant();
+        rep = new spline1dfitreport();
+        lsfit.spline1dfitpenalized(x, y, n, m, rho, ref info, s.innerobj, rep.innerobj);
+        return;
+    }
+    public static void spline1dfitpenalized(double[] x, double[] y, int m, double rho, out int info, out spline1dinterpolant s, out spline1dfitreport rep)
+    {
+        int n;
+        if( (ap.len(x)!=ap.len(y)))
+            throw new alglibexception("Error while calling 'spline1dfitpenalized': looks like one of arguments has wrong size");
+        info = 0;
+        s = new spline1dinterpolant();
+        rep = new spline1dfitreport();
+        n = ap.len(x);
+        lsfit.spline1dfitpenalized(x, y, n, m, rho, ref info, s.innerobj, rep.innerobj);
+
+        return;
+    }
+
+    /*************************************************************************
+    Weighted fitting by penalized cubic spline.
+
+    Equidistant grid with M nodes on [min(x,xc),max(x,xc)] is  used  to  build
+    basis functions. Basis functions are cubic splines with  natural  boundary
+    conditions. Problem is regularized by  adding non-linearity penalty to the
+    usual least squares penalty function:
+
+        S(x) = arg min { LS + P }, where
+        LS   = SUM { w[i]^2*(y[i] - S(x[i]))^2 } - least squares penalty
+        P    = C*10^rho*integral{ S''(x)^2*dx } - non-linearity penalty
+        rho  - tunable constant given by user
+        C    - automatically determined scale parameter,
+               makes penalty invariant with respect to scaling of X, Y, W.
+
+    INPUT PARAMETERS:
+        X   -   points, array[0..N-1].
+        Y   -   function values, array[0..N-1].
+        W   -   weights, array[0..N-1]
+                Each summand in square  sum  of  approximation deviations from
+                given  values  is  multiplied  by  the square of corresponding
+                weight. Fill it by 1's if you don't  want  to  solve  weighted
+                problem.
+        N   -   number of points (optional):
+                * N>0
+                * if given, only first N elements of X/Y/W are processed
+                * if not given, automatically determined from X/Y/W sizes
+        M   -   number of basis functions ( = number_of_nodes), M>=4.
+        Rho -   regularization  constant  passed   by   user.   It   penalizes
+                nonlinearity in the regression spline. It  is  logarithmically
+                scaled,  i.e.  actual  value  of  regularization  constant  is
+                calculated as 10^Rho. It is automatically scaled so that:
+                * Rho=2.0 corresponds to moderate amount of nonlinearity
+                * generally, it should be somewhere in the [-8.0,+8.0]
+                If you do not want to penalize nonlineary,
+                pass small Rho. Values as low as -15 should work.
+
+    OUTPUT PARAMETERS:
+        Info-   same format as in LSFitLinearWC() subroutine.
+                * Info>0    task is solved
+                * Info<=0   an error occured:
+                            -4 means inconvergence of internal SVD or
+                               Cholesky decomposition; problem may be
+                               too ill-conditioned (very rare)
+        S   -   spline interpolant.
+        Rep -   Following fields are set:
+                * RMSError      rms error on the (X,Y).
+                * AvgError      average error on the (X,Y).
+                * AvgRelError   average relative error on the non-zero Y
+                * MaxError      maximum error
+                                NON-WEIGHTED ERRORS ARE CALCULATED
+
+    IMPORTANT:
+        this subroitine doesn't calculate task's condition number for K<>0.
+
+    NOTE 1: additional nodes are added to the spline outside  of  the  fitting
+    interval to force linearity when x<min(x,xc) or x>max(x,xc).  It  is  done
+    for consistency - we penalize non-linearity  at [min(x,xc),max(x,xc)],  so
+    it is natural to force linearity outside of this interval.
+
+    NOTE 2: function automatically sorts points,  so  caller may pass unsorted
+    array.
+
+      -- ALGLIB PROJECT --
+         Copyright 19.10.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void spline1dfitpenalizedw(double[] x, double[] y, double[] w, int n, int m, double rho, out int info, out spline1dinterpolant s, out spline1dfitreport rep)
+    {
+        info = 0;
+        s = new spline1dinterpolant();
+        rep = new spline1dfitreport();
+        lsfit.spline1dfitpenalizedw(x, y, w, n, m, rho, ref info, s.innerobj, rep.innerobj);
+        return;
+    }
+    public static void spline1dfitpenalizedw(double[] x, double[] y, double[] w, int m, double rho, out int info, out spline1dinterpolant s, out spline1dfitreport rep)
+    {
+        int n;
+        if( (ap.len(x)!=ap.len(y)) || (ap.len(x)!=ap.len(w)))
+            throw new alglibexception("Error while calling 'spline1dfitpenalizedw': looks like one of arguments has wrong size");
+        info = 0;
+        s = new spline1dinterpolant();
+        rep = new spline1dfitreport();
+        n = ap.len(x);
+        lsfit.spline1dfitpenalizedw(x, y, w, n, m, rho, ref info, s.innerobj, rep.innerobj);
+
+        return;
+    }
+
+    /*************************************************************************
+    Weighted fitting by cubic  spline,  with constraints on function values or
+    derivatives.
+
+    Equidistant grid with M-2 nodes on [min(x,xc),max(x,xc)] is  used to build
+    basis functions. Basis functions are cubic splines with continuous  second
+    derivatives  and  non-fixed first  derivatives  at  interval  ends.  Small
+    regularizing term is used  when  solving  constrained  tasks  (to  improve
+    stability).
+
+    Task is linear, so linear least squares solver is used. Complexity of this
+    computational scheme is O(N*M^2), mostly dominated by least squares solver
+
+    SEE ALSO
+        Spline1DFitHermiteWC()  -   fitting by Hermite splines (more flexible,
+                                    less smooth)
+        Spline1DFitCubic()      -   "lightweight" fitting  by  cubic  splines,
+                                    without invididual weights and constraints
+
+    INPUT PARAMETERS:
+        X   -   points, array[0..N-1].
+        Y   -   function values, array[0..N-1].
+        W   -   weights, array[0..N-1]
+                Each summand in square  sum  of  approximation deviations from
+                given  values  is  multiplied  by  the square of corresponding
+                weight. Fill it by 1's if you don't  want  to  solve  weighted
+                task.
+        N   -   number of points (optional):
+                * N>0
+                * if given, only first N elements of X/Y/W are processed
+                * if not given, automatically determined from X/Y/W sizes
+        XC  -   points where spline values/derivatives are constrained,
+                array[0..K-1].
+        YC  -   values of constraints, array[0..K-1]
+        DC  -   array[0..K-1], types of constraints:
+                * DC[i]=0   means that S(XC[i])=YC[i]
+                * DC[i]=1   means that S'(XC[i])=YC[i]
+                SEE BELOW FOR IMPORTANT INFORMATION ON CONSTRAINTS
+        K   -   number of constraints (optional):
+                * 0<=K<M.
+                * K=0 means no constraints (XC/YC/DC are not used)
+                * if given, only first K elements of XC/YC/DC are used
+                * if not given, automatically determined from XC/YC/DC
+        M   -   number of basis functions ( = number_of_nodes+2), M>=4.
+
+    OUTPUT PARAMETERS:
+        Info-   same format as in LSFitLinearWC() subroutine.
+                * Info>0    task is solved
+                * Info<=0   an error occured:
+                            -4 means inconvergence of internal SVD
+                            -3 means inconsistent constraints
+        S   -   spline interpolant.
+        Rep -   report, same format as in LSFitLinearWC() subroutine.
+                Following fields are set:
+                * RMSError      rms error on the (X,Y).
+                * AvgError      average error on the (X,Y).
+                * AvgRelError   average relative error on the non-zero Y
+                * MaxError      maximum error
+                                NON-WEIGHTED ERRORS ARE CALCULATED
+
+    IMPORTANT:
+        this subroitine doesn't calculate task's condition number for K<>0.
+
+
+    ORDER OF POINTS
+
+    Subroutine automatically sorts points, so caller may pass unsorted array.
+
+    SETTING CONSTRAINTS - DANGERS AND OPPORTUNITIES:
+
+    Setting constraints can lead  to undesired  results,  like ill-conditioned
+    behavior, or inconsistency being detected. From the other side,  it allows
+    us to improve quality of the fit. Here we summarize  our  experience  with
+    constrained regression splines:
+    * excessive constraints can be inconsistent. Splines are  piecewise  cubic
+      functions, and it is easy to create an example, where  large  number  of
+      constraints  concentrated  in  small  area will result in inconsistency.
+      Just because spline is not flexible enough to satisfy all of  them.  And
+      same constraints spread across the  [min(x),max(x)]  will  be  perfectly
+      consistent.
+    * the more evenly constraints are spread across [min(x),max(x)],  the more
+      chances that they will be consistent
+    * the  greater  is  M (given  fixed  constraints),  the  more chances that
+      constraints will be consistent
+    * in the general case, consistency of constraints IS NOT GUARANTEED.
+    * in the several special cases, however, we CAN guarantee consistency.
+    * one of this cases is constraints  on  the  function  values  AND/OR  its
+      derivatives at the interval boundaries.
+    * another  special  case  is ONE constraint on the function value (OR, but
+      not AND, derivative) anywhere in the interval
+
+    Our final recommendation is to use constraints  WHEN  AND  ONLY  WHEN  you
+    can't solve your task without them. Anything beyond  special  cases  given
+    above is not guaranteed and may result in inconsistency.
+
+
+      -- ALGLIB PROJECT --
+         Copyright 18.08.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void spline1dfitcubicwc(double[] x, double[] y, double[] w, int n, double[] xc, double[] yc, int[] dc, int k, int m, out int info, out spline1dinterpolant s, out spline1dfitreport rep)
+    {
+        info = 0;
+        s = new spline1dinterpolant();
+        rep = new spline1dfitreport();
+        lsfit.spline1dfitcubicwc(x, y, w, n, xc, yc, dc, k, m, ref info, s.innerobj, rep.innerobj);
+        return;
+    }
+    public static void spline1dfitcubicwc(double[] x, double[] y, double[] w, double[] xc, double[] yc, int[] dc, int m, out int info, out spline1dinterpolant s, out spline1dfitreport rep)
+    {
+        int n;
+        int k;
+        if( (ap.len(x)!=ap.len(y)) || (ap.len(x)!=ap.len(w)))
+            throw new alglibexception("Error while calling 'spline1dfitcubicwc': looks like one of arguments has wrong size");
+        if( (ap.len(xc)!=ap.len(yc)) || (ap.len(xc)!=ap.len(dc)))
+            throw new alglibexception("Error while calling 'spline1dfitcubicwc': looks like one of arguments has wrong size");
+        info = 0;
+        s = new spline1dinterpolant();
+        rep = new spline1dfitreport();
+        n = ap.len(x);
+        k = ap.len(xc);
+        lsfit.spline1dfitcubicwc(x, y, w, n, xc, yc, dc, k, m, ref info, s.innerobj, rep.innerobj);
+
+        return;
+    }
+
+    /*************************************************************************
+    Weighted  fitting  by Hermite spline,  with constraints on function values
+    or first derivatives.
+
+    Equidistant grid with M nodes on [min(x,xc),max(x,xc)] is  used  to  build
+    basis functions. Basis functions are Hermite splines.  Small  regularizing
+    term is used when solving constrained tasks (to improve stability).
+
+    Task is linear, so linear least squares solver is used. Complexity of this
+    computational scheme is O(N*M^2), mostly dominated by least squares solver
+
+    SEE ALSO
+        Spline1DFitCubicWC()    -   fitting by Cubic splines (less flexible,
+                                    more smooth)
+        Spline1DFitHermite()    -   "lightweight" Hermite fitting, without
+                                    invididual weights and constraints
+
+    INPUT PARAMETERS:
+        X   -   points, array[0..N-1].
+        Y   -   function values, array[0..N-1].
+        W   -   weights, array[0..N-1]
+                Each summand in square  sum  of  approximation deviations from
+                given  values  is  multiplied  by  the square of corresponding
+                weight. Fill it by 1's if you don't  want  to  solve  weighted
+                task.
+        N   -   number of points (optional):
+                * N>0
+                * if given, only first N elements of X/Y/W are processed
+                * if not given, automatically determined from X/Y/W sizes
+        XC  -   points where spline values/derivatives are constrained,
+                array[0..K-1].
+        YC  -   values of constraints, array[0..K-1]
+        DC  -   array[0..K-1], types of constraints:
+                * DC[i]=0   means that S(XC[i])=YC[i]
+                * DC[i]=1   means that S'(XC[i])=YC[i]
+                SEE BELOW FOR IMPORTANT INFORMATION ON CONSTRAINTS
+        K   -   number of constraints (optional):
+                * 0<=K<M.
+                * K=0 means no constraints (XC/YC/DC are not used)
+                * if given, only first K elements of XC/YC/DC are used
+                * if not given, automatically determined from XC/YC/DC
+        M   -   number of basis functions (= 2 * number of nodes),
+                M>=4,
+                M IS EVEN!
+
+    OUTPUT PARAMETERS:
+        Info-   same format as in LSFitLinearW() subroutine:
+                * Info>0    task is solved
+                * Info<=0   an error occured:
+                            -4 means inconvergence of internal SVD
+                            -3 means inconsistent constraints
+                            -2 means odd M was passed (which is not supported)
+                            -1 means another errors in parameters passed
+                               (N<=0, for example)
+        S   -   spline interpolant.
+        Rep -   report, same format as in LSFitLinearW() subroutine.
+                Following fields are set:
+                * RMSError      rms error on the (X,Y).
+                * AvgError      average error on the (X,Y).
+                * AvgRelError   average relative error on the non-zero Y
+                * MaxError      maximum error
+                                NON-WEIGHTED ERRORS ARE CALCULATED
+
+    IMPORTANT:
+        this subroitine doesn't calculate task's condition number for K<>0.
+
+    IMPORTANT:
+        this subroitine supports only even M's
+
+
+    ORDER OF POINTS
+
+    Subroutine automatically sorts points, so caller may pass unsorted array.
+
+    SETTING CONSTRAINTS - DANGERS AND OPPORTUNITIES:
+
+    Setting constraints can lead  to undesired  results,  like ill-conditioned
+    behavior, or inconsistency being detected. From the other side,  it allows
+    us to improve quality of the fit. Here we summarize  our  experience  with
+    constrained regression splines:
+    * excessive constraints can be inconsistent. Splines are  piecewise  cubic
+      functions, and it is easy to create an example, where  large  number  of
+      constraints  concentrated  in  small  area will result in inconsistency.
+      Just because spline is not flexible enough to satisfy all of  them.  And
+      same constraints spread across the  [min(x),max(x)]  will  be  perfectly
+      consistent.
+    * the more evenly constraints are spread across [min(x),max(x)],  the more
+      chances that they will be consistent
+    * the  greater  is  M (given  fixed  constraints),  the  more chances that
+      constraints will be consistent
+    * in the general case, consistency of constraints is NOT GUARANTEED.
+    * in the several special cases, however, we can guarantee consistency.
+    * one of this cases is  M>=4  and   constraints  on   the  function  value
+      (AND/OR its derivative) at the interval boundaries.
+    * another special case is M>=4  and  ONE  constraint on the function value
+      (OR, BUT NOT AND, derivative) anywhere in [min(x),max(x)]
+
+    Our final recommendation is to use constraints  WHEN  AND  ONLY  when  you
+    can't solve your task without them. Anything beyond  special  cases  given
+    above is not guaranteed and may result in inconsistency.
+
+      -- ALGLIB PROJECT --
+         Copyright 18.08.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void spline1dfithermitewc(double[] x, double[] y, double[] w, int n, double[] xc, double[] yc, int[] dc, int k, int m, out int info, out spline1dinterpolant s, out spline1dfitreport rep)
+    {
+        info = 0;
+        s = new spline1dinterpolant();
+        rep = new spline1dfitreport();
+        lsfit.spline1dfithermitewc(x, y, w, n, xc, yc, dc, k, m, ref info, s.innerobj, rep.innerobj);
+        return;
+    }
+    public static void spline1dfithermitewc(double[] x, double[] y, double[] w, double[] xc, double[] yc, int[] dc, int m, out int info, out spline1dinterpolant s, out spline1dfitreport rep)
+    {
+        int n;
+        int k;
+        if( (ap.len(x)!=ap.len(y)) || (ap.len(x)!=ap.len(w)))
+            throw new alglibexception("Error while calling 'spline1dfithermitewc': looks like one of arguments has wrong size");
+        if( (ap.len(xc)!=ap.len(yc)) || (ap.len(xc)!=ap.len(dc)))
+            throw new alglibexception("Error while calling 'spline1dfithermitewc': looks like one of arguments has wrong size");
+        info = 0;
+        s = new spline1dinterpolant();
+        rep = new spline1dfitreport();
+        n = ap.len(x);
+        k = ap.len(xc);
+        lsfit.spline1dfithermitewc(x, y, w, n, xc, yc, dc, k, m, ref info, s.innerobj, rep.innerobj);
+
+        return;
+    }
+
+    /*************************************************************************
+    Least squares fitting by cubic spline.
+
+    This subroutine is "lightweight" alternative for more complex and feature-
+    rich Spline1DFitCubicWC().  See  Spline1DFitCubicWC() for more information
+    about subroutine parameters (we don't duplicate it here because of length)
+
+      -- ALGLIB PROJECT --
+         Copyright 18.08.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void spline1dfitcubic(double[] x, double[] y, int n, int m, out int info, out spline1dinterpolant s, out spline1dfitreport rep)
+    {
+        info = 0;
+        s = new spline1dinterpolant();
+        rep = new spline1dfitreport();
+        lsfit.spline1dfitcubic(x, y, n, m, ref info, s.innerobj, rep.innerobj);
+        return;
+    }
+    public static void spline1dfitcubic(double[] x, double[] y, int m, out int info, out spline1dinterpolant s, out spline1dfitreport rep)
+    {
+        int n;
+        if( (ap.len(x)!=ap.len(y)))
+            throw new alglibexception("Error while calling 'spline1dfitcubic': looks like one of arguments has wrong size");
+        info = 0;
+        s = new spline1dinterpolant();
+        rep = new spline1dfitreport();
+        n = ap.len(x);
+        lsfit.spline1dfitcubic(x, y, n, m, ref info, s.innerobj, rep.innerobj);
+
+        return;
+    }
+
+    /*************************************************************************
+    Least squares fitting by Hermite spline.
+
+    This subroutine is "lightweight" alternative for more complex and feature-
+    rich Spline1DFitHermiteWC().  See Spline1DFitHermiteWC()  description  for
+    more information about subroutine parameters (we don't duplicate  it  here
+    because of length).
+
+      -- ALGLIB PROJECT --
+         Copyright 18.08.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void spline1dfithermite(double[] x, double[] y, int n, int m, out int info, out spline1dinterpolant s, out spline1dfitreport rep)
+    {
+        info = 0;
+        s = new spline1dinterpolant();
+        rep = new spline1dfitreport();
+        lsfit.spline1dfithermite(x, y, n, m, ref info, s.innerobj, rep.innerobj);
+        return;
+    }
+    public static void spline1dfithermite(double[] x, double[] y, int m, out int info, out spline1dinterpolant s, out spline1dfitreport rep)
+    {
+        int n;
+        if( (ap.len(x)!=ap.len(y)))
+            throw new alglibexception("Error while calling 'spline1dfithermite': looks like one of arguments has wrong size");
+        info = 0;
+        s = new spline1dinterpolant();
+        rep = new spline1dfitreport();
+        n = ap.len(x);
+        lsfit.spline1dfithermite(x, y, n, m, ref info, s.innerobj, rep.innerobj);
+
+        return;
+    }
+
+    /*************************************************************************
+    Weighted linear least squares fitting.
+
+    QR decomposition is used to reduce task to MxM, then triangular solver  or
+    SVD-based solver is used depending on condition number of the  system.  It
+    allows to maximize speed and retain decent accuracy.
+
+    INPUT PARAMETERS:
+        Y       -   array[0..N-1] Function values in  N  points.
+        W       -   array[0..N-1]  Weights  corresponding to function  values.
+                    Each summand in square  sum  of  approximation  deviations
+                    from  given  values  is  multiplied  by  the   square   of
+                    corresponding weight.
+        FMatrix -   a table of basis functions values, array[0..N-1, 0..M-1].
+                    FMatrix[I, J] - value of J-th basis function in I-th point.
+        N       -   number of points used. N>=1.
+        M       -   number of basis functions, M>=1.
+
+    OUTPUT PARAMETERS:
+        Info    -   error code:
+                    * -4    internal SVD decomposition subroutine failed (very
+                            rare and for degenerate systems only)
+                    * -1    incorrect N/M were specified
+                    *  1    task is solved
+        C       -   decomposition coefficients, array[0..M-1]
+        Rep     -   fitting report. Following fields are set:
+                    * Rep.TaskRCond     reciprocal of condition number
+                    * RMSError          rms error on the (X,Y).
+                    * AvgError          average error on the (X,Y).
+                    * AvgRelError       average relative error on the non-zero Y
+                    * MaxError          maximum error
+                                        NON-WEIGHTED ERRORS ARE CALCULATED
+
+      -- ALGLIB --
+         Copyright 17.08.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void lsfitlinearw(double[] y, double[] w, double[,] fmatrix, int n, int m, out int info, out double[] c, out lsfitreport rep)
+    {
+        info = 0;
+        c = new double[0];
+        rep = new lsfitreport();
+        lsfit.lsfitlinearw(y, w, fmatrix, n, m, ref info, ref c, rep.innerobj);
+        return;
+    }
+    public static void lsfitlinearw(double[] y, double[] w, double[,] fmatrix, out int info, out double[] c, out lsfitreport rep)
+    {
+        int n;
+        int m;
+        if( (ap.len(y)!=ap.len(w)) || (ap.len(y)!=ap.rows(fmatrix)))
+            throw new alglibexception("Error while calling 'lsfitlinearw': looks like one of arguments has wrong size");
+        info = 0;
+        c = new double[0];
+        rep = new lsfitreport();
+        n = ap.len(y);
+        m = ap.cols(fmatrix);
+        lsfit.lsfitlinearw(y, w, fmatrix, n, m, ref info, ref c, rep.innerobj);
+
+        return;
+    }
+
+    /*************************************************************************
+    Weighted constained linear least squares fitting.
+
+    This  is  variation  of LSFitLinearW(), which searchs for min|A*x=b| given
+    that  K  additional  constaints  C*x=bc are satisfied. It reduces original
+    task to modified one: min|B*y-d| WITHOUT constraints,  then LSFitLinearW()
+    is called.
+
+    INPUT PARAMETERS:
+        Y       -   array[0..N-1] Function values in  N  points.
+        W       -   array[0..N-1]  Weights  corresponding to function  values.
+                    Each summand in square  sum  of  approximation  deviations
+                    from  given  values  is  multiplied  by  the   square   of
+                    corresponding weight.
+        FMatrix -   a table of basis functions values, array[0..N-1, 0..M-1].
+                    FMatrix[I,J] - value of J-th basis function in I-th point.
+        CMatrix -   a table of constaints, array[0..K-1,0..M].
+                    I-th row of CMatrix corresponds to I-th linear constraint:
+                    CMatrix[I,0]*C[0] + ... + CMatrix[I,M-1]*C[M-1] = CMatrix[I,M]
+        N       -   number of points used. N>=1.
+        M       -   number of basis functions, M>=1.
+        K       -   number of constraints, 0 <= K < M
+                    K=0 corresponds to absence of constraints.
+
+    OUTPUT PARAMETERS:
+        Info    -   error code:
+                    * -4    internal SVD decomposition subroutine failed (very
+                            rare and for degenerate systems only)
+                    * -3    either   too   many  constraints  (M   or   more),
+                            degenerate  constraints   (some   constraints  are
+                            repetead twice) or inconsistent  constraints  were
+                            specified.
+                    *  1    task is solved
+        C       -   decomposition coefficients, array[0..M-1]
+        Rep     -   fitting report. Following fields are set:
+                    * RMSError          rms error on the (X,Y).
+                    * AvgError          average error on the (X,Y).
+                    * AvgRelError       average relative error on the non-zero Y
+                    * MaxError          maximum error
+                                        NON-WEIGHTED ERRORS ARE CALCULATED
+
+    IMPORTANT:
+        this subroitine doesn't calculate task's condition number for K<>0.
+
+      -- ALGLIB --
+         Copyright 07.09.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void lsfitlinearwc(double[] y, double[] w, double[,] fmatrix, double[,] cmatrix, int n, int m, int k, out int info, out double[] c, out lsfitreport rep)
+    {
+        info = 0;
+        c = new double[0];
+        rep = new lsfitreport();
+        lsfit.lsfitlinearwc(y, w, fmatrix, cmatrix, n, m, k, ref info, ref c, rep.innerobj);
+        return;
+    }
+    public static void lsfitlinearwc(double[] y, double[] w, double[,] fmatrix, double[,] cmatrix, out int info, out double[] c, out lsfitreport rep)
+    {
+        int n;
+        int m;
+        int k;
+        if( (ap.len(y)!=ap.len(w)) || (ap.len(y)!=ap.rows(fmatrix)))
+            throw new alglibexception("Error while calling 'lsfitlinearwc': looks like one of arguments has wrong size");
+        if( (ap.cols(fmatrix)!=ap.cols(cmatrix)-1))
+            throw new alglibexception("Error while calling 'lsfitlinearwc': looks like one of arguments has wrong size");
+        info = 0;
+        c = new double[0];
+        rep = new lsfitreport();
+        n = ap.len(y);
+        m = ap.cols(fmatrix);
+        k = ap.rows(cmatrix);
+        lsfit.lsfitlinearwc(y, w, fmatrix, cmatrix, n, m, k, ref info, ref c, rep.innerobj);
+
+        return;
+    }
+
+    /*************************************************************************
+    Linear least squares fitting.
+
+    QR decomposition is used to reduce task to MxM, then triangular solver  or
+    SVD-based solver is used depending on condition number of the  system.  It
+    allows to maximize speed and retain decent accuracy.
+
+    INPUT PARAMETERS:
+        Y       -   array[0..N-1] Function values in  N  points.
+        FMatrix -   a table of basis functions values, array[0..N-1, 0..M-1].
+                    FMatrix[I, J] - value of J-th basis function in I-th point.
+        N       -   number of points used. N>=1.
+        M       -   number of basis functions, M>=1.
+
+    OUTPUT PARAMETERS:
+        Info    -   error code:
+                    * -4    internal SVD decomposition subroutine failed (very
+                            rare and for degenerate systems only)
+                    *  1    task is solved
+        C       -   decomposition coefficients, array[0..M-1]
+        Rep     -   fitting report. Following fields are set:
+                    * Rep.TaskRCond     reciprocal of condition number
+                    * RMSError          rms error on the (X,Y).
+                    * AvgError          average error on the (X,Y).
+                    * AvgRelError       average relative error on the non-zero Y
+                    * MaxError          maximum error
+                                        NON-WEIGHTED ERRORS ARE CALCULATED
+
+      -- ALGLIB --
+         Copyright 17.08.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void lsfitlinear(double[] y, double[,] fmatrix, int n, int m, out int info, out double[] c, out lsfitreport rep)
+    {
+        info = 0;
+        c = new double[0];
+        rep = new lsfitreport();
+        lsfit.lsfitlinear(y, fmatrix, n, m, ref info, ref c, rep.innerobj);
+        return;
+    }
+    public static void lsfitlinear(double[] y, double[,] fmatrix, out int info, out double[] c, out lsfitreport rep)
+    {
+        int n;
+        int m;
+        if( (ap.len(y)!=ap.rows(fmatrix)))
+            throw new alglibexception("Error while calling 'lsfitlinear': looks like one of arguments has wrong size");
+        info = 0;
+        c = new double[0];
+        rep = new lsfitreport();
+        n = ap.len(y);
+        m = ap.cols(fmatrix);
+        lsfit.lsfitlinear(y, fmatrix, n, m, ref info, ref c, rep.innerobj);
+
+        return;
+    }
+
+    /*************************************************************************
+    Constained linear least squares fitting.
+
+    This  is  variation  of LSFitLinear(),  which searchs for min|A*x=b| given
+    that  K  additional  constaints  C*x=bc are satisfied. It reduces original
+    task to modified one: min|B*y-d| WITHOUT constraints,  then  LSFitLinear()
+    is called.
+
+    INPUT PARAMETERS:
+        Y       -   array[0..N-1] Function values in  N  points.
+        FMatrix -   a table of basis functions values, array[0..N-1, 0..M-1].
+                    FMatrix[I,J] - value of J-th basis function in I-th point.
+        CMatrix -   a table of constaints, array[0..K-1,0..M].
+                    I-th row of CMatrix corresponds to I-th linear constraint:
+                    CMatrix[I,0]*C[0] + ... + CMatrix[I,M-1]*C[M-1] = CMatrix[I,M]
+        N       -   number of points used. N>=1.
+        M       -   number of basis functions, M>=1.
+        K       -   number of constraints, 0 <= K < M
+                    K=0 corresponds to absence of constraints.
+
+    OUTPUT PARAMETERS:
+        Info    -   error code:
+                    * -4    internal SVD decomposition subroutine failed (very
+                            rare and for degenerate systems only)
+                    * -3    either   too   many  constraints  (M   or   more),
+                            degenerate  constraints   (some   constraints  are
+                            repetead twice) or inconsistent  constraints  were
+                            specified.
+                    *  1    task is solved
+        C       -   decomposition coefficients, array[0..M-1]
+        Rep     -   fitting report. Following fields are set:
+                    * RMSError          rms error on the (X,Y).
+                    * AvgError          average error on the (X,Y).
+                    * AvgRelError       average relative error on the non-zero Y
+                    * MaxError          maximum error
+                                        NON-WEIGHTED ERRORS ARE CALCULATED
+
+    IMPORTANT:
+        this subroitine doesn't calculate task's condition number for K<>0.
+
+      -- ALGLIB --
+         Copyright 07.09.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void lsfitlinearc(double[] y, double[,] fmatrix, double[,] cmatrix, int n, int m, int k, out int info, out double[] c, out lsfitreport rep)
+    {
+        info = 0;
+        c = new double[0];
+        rep = new lsfitreport();
+        lsfit.lsfitlinearc(y, fmatrix, cmatrix, n, m, k, ref info, ref c, rep.innerobj);
+        return;
+    }
+    public static void lsfitlinearc(double[] y, double[,] fmatrix, double[,] cmatrix, out int info, out double[] c, out lsfitreport rep)
+    {
+        int n;
+        int m;
+        int k;
+        if( (ap.len(y)!=ap.rows(fmatrix)))
+            throw new alglibexception("Error while calling 'lsfitlinearc': looks like one of arguments has wrong size");
+        if( (ap.cols(fmatrix)!=ap.cols(cmatrix)-1))
+            throw new alglibexception("Error while calling 'lsfitlinearc': looks like one of arguments has wrong size");
+        info = 0;
+        c = new double[0];
+        rep = new lsfitreport();
+        n = ap.len(y);
+        m = ap.cols(fmatrix);
+        k = ap.rows(cmatrix);
+        lsfit.lsfitlinearc(y, fmatrix, cmatrix, n, m, k, ref info, ref c, rep.innerobj);
+
+        return;
+    }
+
+    /*************************************************************************
+    Weighted nonlinear least squares fitting using function values only.
+
+    Combination of numerical differentiation and secant updates is used to
+    obtain function Jacobian.
+
+    Nonlinear task min(F(c)) is solved, where
+
+        F(c) = (w[0]*(f(c,x[0])-y[0]))^2 + ... + (w[n-1]*(f(c,x[n-1])-y[n-1]))^2,
+
+        * N is a number of points,
+        * M is a dimension of a space points belong to,
+        * K is a dimension of a space of parameters being fitted,
+        * w is an N-dimensional vector of weight coefficients,
+        * x is a set of N points, each of them is an M-dimensional vector,
+        * c is a K-dimensional vector of parameters being fitted
+
+    This subroutine uses only f(c,x[i]).
+
+    INPUT PARAMETERS:
+        X       -   array[0..N-1,0..M-1], points (one row = one point)
+        Y       -   array[0..N-1], function values.
+        W       -   weights, array[0..N-1]
+        C       -   array[0..K-1], initial approximation to the solution,
+        N       -   number of points, N>1
+        M       -   dimension of space
+        K       -   number of parameters being fitted
+        DiffStep-   numerical differentiation step;
+                    should not be very small or large;
+                    large = loss of accuracy
+                    small = growth of round-off errors
+
+    OUTPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+
+      -- ALGLIB --
+         Copyright 18.10.2008 by Bochkanov Sergey
+    *************************************************************************/
+    public static void lsfitcreatewf(double[,] x, double[] y, double[] w, double[] c, int n, int m, int k, double diffstep, out lsfitstate state)
+    {
+        state = new lsfitstate();
+        lsfit.lsfitcreatewf(x, y, w, c, n, m, k, diffstep, state.innerobj);
+        return;
+    }
+    public static void lsfitcreatewf(double[,] x, double[] y, double[] w, double[] c, double diffstep, out lsfitstate state)
+    {
+        int n;
+        int m;
+        int k;
+        if( (ap.rows(x)!=ap.len(y)) || (ap.rows(x)!=ap.len(w)))
+            throw new alglibexception("Error while calling 'lsfitcreatewf': looks like one of arguments has wrong size");
+        state = new lsfitstate();
+        n = ap.rows(x);
+        m = ap.cols(x);
+        k = ap.len(c);
+        lsfit.lsfitcreatewf(x, y, w, c, n, m, k, diffstep, state.innerobj);
+
+        return;
+    }
+
+    /*************************************************************************
+    Nonlinear least squares fitting using function values only.
+
+    Combination of numerical differentiation and secant updates is used to
+    obtain function Jacobian.
+
+    Nonlinear task min(F(c)) is solved, where
+
+        F(c) = (f(c,x[0])-y[0])^2 + ... + (f(c,x[n-1])-y[n-1])^2,
+
+        * N is a number of points,
+        * M is a dimension of a space points belong to,
+        * K is a dimension of a space of parameters being fitted,
+        * w is an N-dimensional vector of weight coefficients,
+        * x is a set of N points, each of them is an M-dimensional vector,
+        * c is a K-dimensional vector of parameters being fitted
+
+    This subroutine uses only f(c,x[i]).
+
+    INPUT PARAMETERS:
+        X       -   array[0..N-1,0..M-1], points (one row = one point)
+        Y       -   array[0..N-1], function values.
+        C       -   array[0..K-1], initial approximation to the solution,
+        N       -   number of points, N>1
+        M       -   dimension of space
+        K       -   number of parameters being fitted
+        DiffStep-   numerical differentiation step;
+                    should not be very small or large;
+                    large = loss of accuracy
+                    small = growth of round-off errors
+
+    OUTPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+
+      -- ALGLIB --
+         Copyright 18.10.2008 by Bochkanov Sergey
+    *************************************************************************/
+    public static void lsfitcreatef(double[,] x, double[] y, double[] c, int n, int m, int k, double diffstep, out lsfitstate state)
+    {
+        state = new lsfitstate();
+        lsfit.lsfitcreatef(x, y, c, n, m, k, diffstep, state.innerobj);
+        return;
+    }
+    public static void lsfitcreatef(double[,] x, double[] y, double[] c, double diffstep, out lsfitstate state)
+    {
+        int n;
+        int m;
+        int k;
+        if( (ap.rows(x)!=ap.len(y)))
+            throw new alglibexception("Error while calling 'lsfitcreatef': looks like one of arguments has wrong size");
+        state = new lsfitstate();
+        n = ap.rows(x);
+        m = ap.cols(x);
+        k = ap.len(c);
+        lsfit.lsfitcreatef(x, y, c, n, m, k, diffstep, state.innerobj);
+
+        return;
+    }
+
+    /*************************************************************************
+    Weighted nonlinear least squares fitting using gradient only.
+
+    Nonlinear task min(F(c)) is solved, where
+
+        F(c) = (w[0]*(f(c,x[0])-y[0]))^2 + ... + (w[n-1]*(f(c,x[n-1])-y[n-1]))^2,
+
+        * N is a number of points,
+        * M is a dimension of a space points belong to,
+        * K is a dimension of a space of parameters being fitted,
+        * w is an N-dimensional vector of weight coefficients,
+        * x is a set of N points, each of them is an M-dimensional vector,
+        * c is a K-dimensional vector of parameters being fitted
+
+    This subroutine uses only f(c,x[i]) and its gradient.
+
+    INPUT PARAMETERS:
+        X       -   array[0..N-1,0..M-1], points (one row = one point)
+        Y       -   array[0..N-1], function values.
+        W       -   weights, array[0..N-1]
+        C       -   array[0..K-1], initial approximation to the solution,
+        N       -   number of points, N>1
+        M       -   dimension of space
+        K       -   number of parameters being fitted
+        CheapFG -   boolean flag, which is:
+                    * True  if both function and gradient calculation complexity
+                            are less than O(M^2).  An improved  algorithm  can
+                            be  used  which corresponds  to  FGJ  scheme  from
+                            MINLM unit.
+                    * False otherwise.
+                            Standard Jacibian-bases  Levenberg-Marquardt  algo
+                            will be used (FJ scheme).
+
+    OUTPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+
+    See also:
+        LSFitResults
+        LSFitCreateFG (fitting without weights)
+        LSFitCreateWFGH (fitting using Hessian)
+        LSFitCreateFGH (fitting using Hessian, without weights)
+
+      -- ALGLIB --
+         Copyright 17.08.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void lsfitcreatewfg(double[,] x, double[] y, double[] w, double[] c, int n, int m, int k, bool cheapfg, out lsfitstate state)
+    {
+        state = new lsfitstate();
+        lsfit.lsfitcreatewfg(x, y, w, c, n, m, k, cheapfg, state.innerobj);
+        return;
+    }
+    public static void lsfitcreatewfg(double[,] x, double[] y, double[] w, double[] c, bool cheapfg, out lsfitstate state)
+    {
+        int n;
+        int m;
+        int k;
+        if( (ap.rows(x)!=ap.len(y)) || (ap.rows(x)!=ap.len(w)))
+            throw new alglibexception("Error while calling 'lsfitcreatewfg': looks like one of arguments has wrong size");
+        state = new lsfitstate();
+        n = ap.rows(x);
+        m = ap.cols(x);
+        k = ap.len(c);
+        lsfit.lsfitcreatewfg(x, y, w, c, n, m, k, cheapfg, state.innerobj);
+
+        return;
+    }
+
+    /*************************************************************************
+    Nonlinear least squares fitting using gradient only, without individual
+    weights.
+
+    Nonlinear task min(F(c)) is solved, where
+
+        F(c) = ((f(c,x[0])-y[0]))^2 + ... + ((f(c,x[n-1])-y[n-1]))^2,
+
+        * N is a number of points,
+        * M is a dimension of a space points belong to,
+        * K is a dimension of a space of parameters being fitted,
+        * x is a set of N points, each of them is an M-dimensional vector,
+        * c is a K-dimensional vector of parameters being fitted
+
+    This subroutine uses only f(c,x[i]) and its gradient.
+
+    INPUT PARAMETERS:
+        X       -   array[0..N-1,0..M-1], points (one row = one point)
+        Y       -   array[0..N-1], function values.
+        C       -   array[0..K-1], initial approximation to the solution,
+        N       -   number of points, N>1
+        M       -   dimension of space
+        K       -   number of parameters being fitted
+        CheapFG -   boolean flag, which is:
+                    * True  if both function and gradient calculation complexity
+                            are less than O(M^2).  An improved  algorithm  can
+                            be  used  which corresponds  to  FGJ  scheme  from
+                            MINLM unit.
+                    * False otherwise.
+                            Standard Jacibian-bases  Levenberg-Marquardt  algo
+                            will be used (FJ scheme).
+
+    OUTPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+
+      -- ALGLIB --
+         Copyright 17.08.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void lsfitcreatefg(double[,] x, double[] y, double[] c, int n, int m, int k, bool cheapfg, out lsfitstate state)
+    {
+        state = new lsfitstate();
+        lsfit.lsfitcreatefg(x, y, c, n, m, k, cheapfg, state.innerobj);
+        return;
+    }
+    public static void lsfitcreatefg(double[,] x, double[] y, double[] c, bool cheapfg, out lsfitstate state)
+    {
+        int n;
+        int m;
+        int k;
+        if( (ap.rows(x)!=ap.len(y)))
+            throw new alglibexception("Error while calling 'lsfitcreatefg': looks like one of arguments has wrong size");
+        state = new lsfitstate();
+        n = ap.rows(x);
+        m = ap.cols(x);
+        k = ap.len(c);
+        lsfit.lsfitcreatefg(x, y, c, n, m, k, cheapfg, state.innerobj);
+
+        return;
+    }
+
+    /*************************************************************************
+    Weighted nonlinear least squares fitting using gradient/Hessian.
+
+    Nonlinear task min(F(c)) is solved, where
+
+        F(c) = (w[0]*(f(c,x[0])-y[0]))^2 + ... + (w[n-1]*(f(c,x[n-1])-y[n-1]))^2,
+
+        * N is a number of points,
+        * M is a dimension of a space points belong to,
+        * K is a dimension of a space of parameters being fitted,
+        * w is an N-dimensional vector of weight coefficients,
+        * x is a set of N points, each of them is an M-dimensional vector,
+        * c is a K-dimensional vector of parameters being fitted
+
+    This subroutine uses f(c,x[i]), its gradient and its Hessian.
+
+    INPUT PARAMETERS:
+        X       -   array[0..N-1,0..M-1], points (one row = one point)
+        Y       -   array[0..N-1], function values.
+        W       -   weights, array[0..N-1]
+        C       -   array[0..K-1], initial approximation to the solution,
+        N       -   number of points, N>1
+        M       -   dimension of space
+        K       -   number of parameters being fitted
+
+    OUTPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+
+      -- ALGLIB --
+         Copyright 17.08.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void lsfitcreatewfgh(double[,] x, double[] y, double[] w, double[] c, int n, int m, int k, out lsfitstate state)
+    {
+        state = new lsfitstate();
+        lsfit.lsfitcreatewfgh(x, y, w, c, n, m, k, state.innerobj);
+        return;
+    }
+    public static void lsfitcreatewfgh(double[,] x, double[] y, double[] w, double[] c, out lsfitstate state)
+    {
+        int n;
+        int m;
+        int k;
+        if( (ap.rows(x)!=ap.len(y)) || (ap.rows(x)!=ap.len(w)))
+            throw new alglibexception("Error while calling 'lsfitcreatewfgh': looks like one of arguments has wrong size");
+        state = new lsfitstate();
+        n = ap.rows(x);
+        m = ap.cols(x);
+        k = ap.len(c);
+        lsfit.lsfitcreatewfgh(x, y, w, c, n, m, k, state.innerobj);
+
+        return;
+    }
+
+    /*************************************************************************
+    Nonlinear least squares fitting using gradient/Hessian, without individial
+    weights.
+
+    Nonlinear task min(F(c)) is solved, where
+
+        F(c) = ((f(c,x[0])-y[0]))^2 + ... + ((f(c,x[n-1])-y[n-1]))^2,
+
+        * N is a number of points,
+        * M is a dimension of a space points belong to,
+        * K is a dimension of a space of parameters being fitted,
+        * x is a set of N points, each of them is an M-dimensional vector,
+        * c is a K-dimensional vector of parameters being fitted
+
+    This subroutine uses f(c,x[i]), its gradient and its Hessian.
+
+    INPUT PARAMETERS:
+        X       -   array[0..N-1,0..M-1], points (one row = one point)
+        Y       -   array[0..N-1], function values.
+        C       -   array[0..K-1], initial approximation to the solution,
+        N       -   number of points, N>1
+        M       -   dimension of space
+        K       -   number of parameters being fitted
+
+    OUTPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+
+
+      -- ALGLIB --
+         Copyright 17.08.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void lsfitcreatefgh(double[,] x, double[] y, double[] c, int n, int m, int k, out lsfitstate state)
+    {
+        state = new lsfitstate();
+        lsfit.lsfitcreatefgh(x, y, c, n, m, k, state.innerobj);
+        return;
+    }
+    public static void lsfitcreatefgh(double[,] x, double[] y, double[] c, out lsfitstate state)
+    {
+        int n;
+        int m;
+        int k;
+        if( (ap.rows(x)!=ap.len(y)))
+            throw new alglibexception("Error while calling 'lsfitcreatefgh': looks like one of arguments has wrong size");
+        state = new lsfitstate();
+        n = ap.rows(x);
+        m = ap.cols(x);
+        k = ap.len(c);
+        lsfit.lsfitcreatefgh(x, y, c, n, m, k, state.innerobj);
+
+        return;
+    }
+
+    /*************************************************************************
+    Stopping conditions for nonlinear least squares fitting.
+
+    INPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+        EpsF    -   stopping criterion. Algorithm stops if
+                    |F(k+1)-F(k)| <= EpsF*max{|F(k)|, |F(k+1)|, 1}
+        EpsX    -   >=0
+                    The subroutine finishes its work if  on  k+1-th  iteration
+                    the condition |v|<=EpsX is fulfilled, where:
+                    * |.| means Euclidian norm
+                    * v - scaled step vector, v[i]=dx[i]/s[i]
+                    * dx - ste pvector, dx=X(k+1)-X(k)
+                    * s - scaling coefficients set by LSFitSetScale()
+        MaxIts  -   maximum number of iterations. If MaxIts=0, the  number  of
+                    iterations   is    unlimited.   Only   Levenberg-Marquardt
+                    iterations  are  counted  (L-BFGS/CG  iterations  are  NOT
+                    counted because their cost is very low compared to that of
+                    LM).
+
+    NOTE
+
+    Passing EpsF=0, EpsX=0 and MaxIts=0 (simultaneously) will lead to automatic
+    stopping criterion selection (according to the scheme used by MINLM unit).
+
+
+      -- ALGLIB --
+         Copyright 17.08.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void lsfitsetcond(lsfitstate state, double epsf, double epsx, int maxits)
+    {
+
+        lsfit.lsfitsetcond(state.innerobj, epsf, epsx, maxits);
+        return;
+    }
+
+    /*************************************************************************
+    This function sets maximum step length
+
+    INPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+        StpMax  -   maximum step length, >=0. Set StpMax to 0.0,  if you don't
+                    want to limit step length.
+
+    Use this subroutine when you optimize target function which contains exp()
+    or  other  fast  growing  functions,  and optimization algorithm makes too
+    large  steps  which  leads  to overflow. This function allows us to reject
+    steps  that  are  too  large  (and  therefore  expose  us  to the possible
+    overflow) without actually calculating function value at the x+stp*d.
+
+    NOTE: non-zero StpMax leads to moderate  performance  degradation  because
+    intermediate  step  of  preconditioned L-BFGS optimization is incompatible
+    with limits on step size.
+
+      -- ALGLIB --
+         Copyright 02.04.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void lsfitsetstpmax(lsfitstate state, double stpmax)
+    {
+
+        lsfit.lsfitsetstpmax(state.innerobj, stpmax);
+        return;
+    }
+
+    /*************************************************************************
+    This function turns on/off reporting.
+
+    INPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+        NeedXRep-   whether iteration reports are needed or not
+
+    When reports are needed, State.C (current parameters) and State.F (current
+    value of fitting function) are reported.
+
+
+      -- ALGLIB --
+         Copyright 15.08.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void lsfitsetxrep(lsfitstate state, bool needxrep)
+    {
+
+        lsfit.lsfitsetxrep(state.innerobj, needxrep);
+        return;
+    }
+
+    /*************************************************************************
+    This function sets scaling coefficients for underlying optimizer.
+
+    ALGLIB optimizers use scaling matrices to test stopping  conditions  (step
+    size and gradient are scaled before comparison with tolerances).  Scale of
+    the I-th variable is a translation invariant measure of:
+    a) "how large" the variable is
+    b) how large the step should be to make significant changes in the function
+
+    Generally, scale is NOT considered to be a form of preconditioner.  But LM
+    optimizer is unique in that it uses scaling matrix both  in  the  stopping
+    condition tests and as Marquardt damping factor.
+
+    Proper scaling is very important for the algorithm performance. It is less
+    important for the quality of results, but still has some influence (it  is
+    easier  to  converge  when  variables  are  properly  scaled, so premature
+    stopping is possible when very badly scalled variables are  combined  with
+    relaxed stopping conditions).
+
+    INPUT PARAMETERS:
+        State   -   structure stores algorithm state
+        S       -   array[N], non-zero scaling coefficients
+                    S[i] may be negative, sign doesn't matter.
+
+      -- ALGLIB --
+         Copyright 14.01.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void lsfitsetscale(lsfitstate state, double[] s)
+    {
+
+        lsfit.lsfitsetscale(state.innerobj, s);
+        return;
+    }
+
+    /*************************************************************************
+    This function sets boundary constraints for underlying optimizer
+
+    Boundary constraints are inactive by default (after initial creation).
+    They are preserved until explicitly turned off with another SetBC() call.
+
+    INPUT PARAMETERS:
+        State   -   structure stores algorithm state
+        BndL    -   lower bounds, array[K].
+                    If some (all) variables are unbounded, you may specify
+                    very small number or -INF (latter is recommended because
+                    it will allow solver to use better algorithm).
+        BndU    -   upper bounds, array[K].
+                    If some (all) variables are unbounded, you may specify
+                    very large number or +INF (latter is recommended because
+                    it will allow solver to use better algorithm).
+
+    NOTE 1: it is possible to specify BndL[i]=BndU[i]. In this case I-th
+    variable will be "frozen" at X[i]=BndL[i]=BndU[i].
+
+    NOTE 2: unlike other constrained optimization algorithms, this solver  has
+    following useful properties:
+    * bound constraints are always satisfied exactly
+    * function is evaluated only INSIDE area specified by bound constraints
+
+      -- ALGLIB --
+         Copyright 14.01.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void lsfitsetbc(lsfitstate state, double[] bndl, double[] bndu)
+    {
+
+        lsfit.lsfitsetbc(state.innerobj, bndl, bndu);
+        return;
+    }
+
+    /*************************************************************************
+    This function provides reverse communication interface
+    Reverse communication interface is not documented or recommended to use.
+    See below for functions which provide better documented API
+    *************************************************************************/
+    public static bool lsfititeration(lsfitstate state)
+    {
+
+        bool result = lsfit.lsfititeration(state.innerobj);
+        return result;
+    }
+    /*************************************************************************
+    This family of functions is used to launcn iterations of nonlinear fitter
+
+    These functions accept following parameters:
+        func    -   callback which calculates function (or merit function)
+                    value func at given point x
+        grad    -   callback which calculates function (or merit function)
+                    value func and gradient grad at given point x
+        hess    -   callback which calculates function (or merit function)
+                    value func, gradient grad and Hessian hess at given point x
+        rep     -   optional callback which is called after each iteration
+                    can be null
+        obj     -   optional object which is passed to func/grad/hess/jac/rep
+                    can be null
+
+    NOTES:
+
+    1. this algorithm is somewhat unusual because it works with  parameterized
+       function f(C,X), where X is a function argument (we  have  many  points
+       which are characterized by different  argument  values),  and  C  is  a
+       parameter to fit.
+
+       For example, if we want to do linear fit by f(c0,c1,x) = c0*x+c1,  then
+       x will be argument, and {c0,c1} will be parameters.
+
+       It is important to understand that this algorithm finds minimum in  the
+       space of function PARAMETERS (not arguments), so it  needs  derivatives
+       of f() with respect to C, not X.
+
+       In the example above it will need f=c0*x+c1 and {df/dc0,df/dc1} = {x,1}
+       instead of {df/dx} = {c0}.
+
+    2. Callback functions accept C as the first parameter, and X as the second
+
+    3. If  state  was  created  with  LSFitCreateFG(),  algorithm  needs  just
+       function   and   its   gradient,   but   if   state   was  created with
+       LSFitCreateFGH(), algorithm will need function, gradient and Hessian.
+
+       According  to  the  said  above,  there  ase  several  versions of this
+       function, which accept different sets of callbacks.
+
+       This flexibility opens way to subtle errors - you may create state with
+       LSFitCreateFGH() (optimization using Hessian), but call function  which
+       does not accept Hessian. So when algorithm will request Hessian,  there
+       will be no callback to call. In this case exception will be thrown.
+
+       Be careful to avoid such errors because there is no way to find them at
+       compile time - you can see them at runtime only.
+
+      -- ALGLIB --
+         Copyright 17.08.2009 by Bochkanov Sergey
+
+    *************************************************************************/
+    public static void lsfitfit(lsfitstate state, ndimensional_pfunc func, ndimensional_rep rep, object obj)
+    {
+        if( func==null )
+            throw new alglibexception("ALGLIB: error in 'lsfitfit()' (func is null)");
+        while( alglib.lsfititeration(state) )
+        {
+            if( state.needf )
+            {
+                func(state.c, state.x, ref state.innerobj.f, obj);
+                continue;
+            }
+            if( state.innerobj.xupdated )
+            {
+                if( rep!=null )
+                    rep(state.innerobj.c, state.innerobj.f, obj);
+                continue;
+            }
+            throw new alglibexception("ALGLIB: error in 'lsfitfit' (some derivatives were not provided?)");
+        }
+    }
+
+
+    public static void lsfitfit(lsfitstate state, ndimensional_pfunc func, ndimensional_pgrad grad, ndimensional_rep rep, object obj)
+    {
+        if( func==null )
+            throw new alglibexception("ALGLIB: error in 'lsfitfit()' (func is null)");
+        if( grad==null )
+            throw new alglibexception("ALGLIB: error in 'lsfitfit()' (grad is null)");
+        while( alglib.lsfititeration(state) )
+        {
+            if( state.needf )
+            {
+                func(state.c, state.x, ref state.innerobj.f, obj);
+                continue;
+            }
+            if( state.needfg )
+            {
+                grad(state.c, state.x, ref state.innerobj.f, state.innerobj.g, obj);
+                continue;
+            }
+            if( state.innerobj.xupdated )
+            {
+                if( rep!=null )
+                    rep(state.innerobj.c, state.innerobj.f, obj);
+                continue;
+            }
+            throw new alglibexception("ALGLIB: error in 'lsfitfit' (some derivatives were not provided?)");
+        }
+    }
+
+
+    public static void lsfitfit(lsfitstate state, ndimensional_pfunc func, ndimensional_pgrad grad, ndimensional_phess hess, ndimensional_rep rep, object obj)
+    {
+        if( func==null )
+            throw new alglibexception("ALGLIB: error in 'lsfitfit()' (func is null)");
+        if( grad==null )
+            throw new alglibexception("ALGLIB: error in 'lsfitfit()' (grad is null)");
+        if( hess==null )
+            throw new alglibexception("ALGLIB: error in 'lsfitfit()' (hess is null)");
+        while( alglib.lsfititeration(state) )
+        {
+            if( state.needf )
+            {
+                func(state.c, state.x, ref state.innerobj.f, obj);
+                continue;
+            }
+            if( state.needfg )
+            {
+                grad(state.c, state.x, ref state.innerobj.f, state.innerobj.g, obj);
+                continue;
+            }
+            if( state.needfgh )
+            {
+                hess(state.c, state.x, ref state.innerobj.f, state.innerobj.g, state.innerobj.h, obj);
+                continue;
+            }
+            if( state.innerobj.xupdated )
+            {
+                if( rep!=null )
+                    rep(state.innerobj.c, state.innerobj.f, obj);
+                continue;
+            }
+            throw new alglibexception("ALGLIB: error in 'lsfitfit' (some derivatives were not provided?)");
+        }
+    }
+
+
+
+    /*************************************************************************
+    Nonlinear least squares fitting results.
+
+    Called after return from LSFitFit().
+
+    INPUT PARAMETERS:
+        State   -   algorithm state
+
+    OUTPUT PARAMETERS:
+        Info    -   completetion code:
+                        *  1    relative function improvement is no more than
+                                EpsF.
+                        *  2    relative step is no more than EpsX.
+                        *  4    gradient norm is no more than EpsG
+                        *  5    MaxIts steps was taken
+                        *  7    stopping conditions are too stringent,
+                                further improvement is impossible
+        C       -   array[0..K-1], solution
+        Rep     -   optimization report. Following fields are set:
+                    * Rep.TerminationType completetion code:
+                    * RMSError          rms error on the (X,Y).
+                    * AvgError          average error on the (X,Y).
+                    * AvgRelError       average relative error on the non-zero Y
+                    * MaxError          maximum error
+                                        NON-WEIGHTED ERRORS ARE CALCULATED
+                    * WRMSError         weighted rms error on the (X,Y).
+
+
+      -- ALGLIB --
+         Copyright 17.08.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void lsfitresults(lsfitstate state, out int info, out double[] c, out lsfitreport rep)
+    {
+        info = 0;
+        c = new double[0];
+        rep = new lsfitreport();
+        lsfit.lsfitresults(state.innerobj, ref info, ref c, rep.innerobj);
+        return;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    Parametric spline inteprolant: 2-dimensional curve.
+
+    You should not try to access its members directly - use PSpline2XXXXXXXX()
+    functions instead.
+    *************************************************************************/
+    public class pspline2interpolant
+    {
+        //
+        // Public declarations
+        //
+
+        public pspline2interpolant()
+        {
+            _innerobj = new pspline.pspline2interpolant();
+        }
+
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private pspline.pspline2interpolant _innerobj;
+        public pspline.pspline2interpolant innerobj { get { return _innerobj; } }
+        public pspline2interpolant(pspline.pspline2interpolant obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+
+    /*************************************************************************
+    Parametric spline inteprolant: 3-dimensional curve.
+
+    You should not try to access its members directly - use PSpline3XXXXXXXX()
+    functions instead.
+    *************************************************************************/
+    public class pspline3interpolant
+    {
+        //
+        // Public declarations
+        //
+
+        public pspline3interpolant()
+        {
+            _innerobj = new pspline.pspline3interpolant();
+        }
+
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private pspline.pspline3interpolant _innerobj;
+        public pspline.pspline3interpolant innerobj { get { return _innerobj; } }
+        public pspline3interpolant(pspline.pspline3interpolant obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+    /*************************************************************************
+    This function  builds  non-periodic 2-dimensional parametric spline  which
+    starts at (X[0],Y[0]) and ends at (X[N-1],Y[N-1]).
+
+    INPUT PARAMETERS:
+        XY  -   points, array[0..N-1,0..1].
+                XY[I,0:1] corresponds to the Ith point.
+                Order of points is important!
+        N   -   points count, N>=5 for Akima splines, N>=2 for other types  of
+                splines.
+        ST  -   spline type:
+                * 0     Akima spline
+                * 1     parabolically terminated Catmull-Rom spline (Tension=0)
+                * 2     parabolically terminated cubic spline
+        PT  -   parameterization type:
+                * 0     uniform
+                * 1     chord length
+                * 2     centripetal
+
+    OUTPUT PARAMETERS:
+        P   -   parametric spline interpolant
+
+
+    NOTES:
+    * this function  assumes  that  there all consequent points  are distinct.
+      I.e. (x0,y0)<>(x1,y1),  (x1,y1)<>(x2,y2),  (x2,y2)<>(x3,y3)  and  so on.
+      However, non-consequent points may coincide, i.e. we can  have  (x0,y0)=
+      =(x2,y2).
+
+      -- ALGLIB PROJECT --
+         Copyright 28.05.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void pspline2build(double[,] xy, int n, int st, int pt, out pspline2interpolant p)
+    {
+        p = new pspline2interpolant();
+        pspline.pspline2build(xy, n, st, pt, p.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    This function  builds  non-periodic 3-dimensional parametric spline  which
+    starts at (X[0],Y[0],Z[0]) and ends at (X[N-1],Y[N-1],Z[N-1]).
+
+    Same as PSpline2Build() function, but for 3D, so we  won't  duplicate  its
+    description here.
+
+      -- ALGLIB PROJECT --
+         Copyright 28.05.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void pspline3build(double[,] xy, int n, int st, int pt, out pspline3interpolant p)
+    {
+        p = new pspline3interpolant();
+        pspline.pspline3build(xy, n, st, pt, p.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    This  function  builds  periodic  2-dimensional  parametric  spline  which
+    starts at (X[0],Y[0]), goes through all points to (X[N-1],Y[N-1]) and then
+    back to (X[0],Y[0]).
+
+    INPUT PARAMETERS:
+        XY  -   points, array[0..N-1,0..1].
+                XY[I,0:1] corresponds to the Ith point.
+                XY[N-1,0:1] must be different from XY[0,0:1].
+                Order of points is important!
+        N   -   points count, N>=3 for other types of splines.
+        ST  -   spline type:
+                * 1     Catmull-Rom spline (Tension=0) with cyclic boundary conditions
+                * 2     cubic spline with cyclic boundary conditions
+        PT  -   parameterization type:
+                * 0     uniform
+                * 1     chord length
+                * 2     centripetal
+
+    OUTPUT PARAMETERS:
+        P   -   parametric spline interpolant
+
+
+    NOTES:
+    * this function  assumes  that there all consequent points  are  distinct.
+      I.e. (x0,y0)<>(x1,y1), (x1,y1)<>(x2,y2),  (x2,y2)<>(x3,y3)  and  so  on.
+      However, non-consequent points may coincide, i.e. we can  have  (x0,y0)=
+      =(x2,y2).
+    * last point of sequence is NOT equal to the first  point.  You  shouldn't
+      make curve "explicitly periodic" by making them equal.
+
+      -- ALGLIB PROJECT --
+         Copyright 28.05.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void pspline2buildperiodic(double[,] xy, int n, int st, int pt, out pspline2interpolant p)
+    {
+        p = new pspline2interpolant();
+        pspline.pspline2buildperiodic(xy, n, st, pt, p.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    This  function  builds  periodic  3-dimensional  parametric  spline  which
+    starts at (X[0],Y[0],Z[0]), goes through all points to (X[N-1],Y[N-1],Z[N-1])
+    and then back to (X[0],Y[0],Z[0]).
+
+    Same as PSpline2Build() function, but for 3D, so we  won't  duplicate  its
+    description here.
+
+      -- ALGLIB PROJECT --
+         Copyright 28.05.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void pspline3buildperiodic(double[,] xy, int n, int st, int pt, out pspline3interpolant p)
+    {
+        p = new pspline3interpolant();
+        pspline.pspline3buildperiodic(xy, n, st, pt, p.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    This function returns vector of parameter values correspoding to points.
+
+    I.e. for P created from (X[0],Y[0])...(X[N-1],Y[N-1]) and U=TValues(P)  we
+    have
+        (X[0],Y[0]) = PSpline2Calc(P,U[0]),
+        (X[1],Y[1]) = PSpline2Calc(P,U[1]),
+        (X[2],Y[2]) = PSpline2Calc(P,U[2]),
+        ...
+
+    INPUT PARAMETERS:
+        P   -   parametric spline interpolant
+
+    OUTPUT PARAMETERS:
+        N   -   array size
+        T   -   array[0..N-1]
+
+
+    NOTES:
+    * for non-periodic splines U[0]=0, U[0]<U[1]<...<U[N-1], U[N-1]=1
+    * for periodic splines     U[0]=0, U[0]<U[1]<...<U[N-1], U[N-1]<1
+
+      -- ALGLIB PROJECT --
+         Copyright 28.05.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void pspline2parametervalues(pspline2interpolant p, out int n, out double[] t)
+    {
+        n = 0;
+        t = new double[0];
+        pspline.pspline2parametervalues(p.innerobj, ref n, ref t);
+        return;
+    }
+
+    /*************************************************************************
+    This function returns vector of parameter values correspoding to points.
+
+    Same as PSpline2ParameterValues(), but for 3D.
+
+      -- ALGLIB PROJECT --
+         Copyright 28.05.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void pspline3parametervalues(pspline3interpolant p, out int n, out double[] t)
+    {
+        n = 0;
+        t = new double[0];
+        pspline.pspline3parametervalues(p.innerobj, ref n, ref t);
+        return;
+    }
+
+    /*************************************************************************
+    This function  calculates  the value of the parametric spline for a  given
+    value of parameter T
+
+    INPUT PARAMETERS:
+        P   -   parametric spline interpolant
+        T   -   point:
+                * T in [0,1] corresponds to interval spanned by points
+                * for non-periodic splines T<0 (or T>1) correspond to parts of
+                  the curve before the first (after the last) point
+                * for periodic splines T<0 (or T>1) are projected  into  [0,1]
+                  by making T=T-floor(T).
+
+    OUTPUT PARAMETERS:
+        X   -   X-position
+        Y   -   Y-position
+
+
+      -- ALGLIB PROJECT --
+         Copyright 28.05.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void pspline2calc(pspline2interpolant p, double t, out double x, out double y)
+    {
+        x = 0;
+        y = 0;
+        pspline.pspline2calc(p.innerobj, t, ref x, ref y);
+        return;
+    }
+
+    /*************************************************************************
+    This function  calculates  the value of the parametric spline for a  given
+    value of parameter T.
+
+    INPUT PARAMETERS:
+        P   -   parametric spline interpolant
+        T   -   point:
+                * T in [0,1] corresponds to interval spanned by points
+                * for non-periodic splines T<0 (or T>1) correspond to parts of
+                  the curve before the first (after the last) point
+                * for periodic splines T<0 (or T>1) are projected  into  [0,1]
+                  by making T=T-floor(T).
+
+    OUTPUT PARAMETERS:
+        X   -   X-position
+        Y   -   Y-position
+        Z   -   Z-position
+
+
+      -- ALGLIB PROJECT --
+         Copyright 28.05.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void pspline3calc(pspline3interpolant p, double t, out double x, out double y, out double z)
+    {
+        x = 0;
+        y = 0;
+        z = 0;
+        pspline.pspline3calc(p.innerobj, t, ref x, ref y, ref z);
+        return;
+    }
+
+    /*************************************************************************
+    This function  calculates  tangent vector for a given value of parameter T
+
+    INPUT PARAMETERS:
+        P   -   parametric spline interpolant
+        T   -   point:
+                * T in [0,1] corresponds to interval spanned by points
+                * for non-periodic splines T<0 (or T>1) correspond to parts of
+                  the curve before the first (after the last) point
+                * for periodic splines T<0 (or T>1) are projected  into  [0,1]
+                  by making T=T-floor(T).
+
+    OUTPUT PARAMETERS:
+        X    -   X-component of tangent vector (normalized)
+        Y    -   Y-component of tangent vector (normalized)
+
+    NOTE:
+        X^2+Y^2 is either 1 (for non-zero tangent vector) or 0.
+
+
+      -- ALGLIB PROJECT --
+         Copyright 28.05.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void pspline2tangent(pspline2interpolant p, double t, out double x, out double y)
+    {
+        x = 0;
+        y = 0;
+        pspline.pspline2tangent(p.innerobj, t, ref x, ref y);
+        return;
+    }
+
+    /*************************************************************************
+    This function  calculates  tangent vector for a given value of parameter T
+
+    INPUT PARAMETERS:
+        P   -   parametric spline interpolant
+        T   -   point:
+                * T in [0,1] corresponds to interval spanned by points
+                * for non-periodic splines T<0 (or T>1) correspond to parts of
+                  the curve before the first (after the last) point
+                * for periodic splines T<0 (or T>1) are projected  into  [0,1]
+                  by making T=T-floor(T).
+
+    OUTPUT PARAMETERS:
+        X    -   X-component of tangent vector (normalized)
+        Y    -   Y-component of tangent vector (normalized)
+        Z    -   Z-component of tangent vector (normalized)
+
+    NOTE:
+        X^2+Y^2+Z^2 is either 1 (for non-zero tangent vector) or 0.
+
+
+      -- ALGLIB PROJECT --
+         Copyright 28.05.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void pspline3tangent(pspline3interpolant p, double t, out double x, out double y, out double z)
+    {
+        x = 0;
+        y = 0;
+        z = 0;
+        pspline.pspline3tangent(p.innerobj, t, ref x, ref y, ref z);
+        return;
+    }
+
+    /*************************************************************************
+    This function calculates derivative, i.e. it returns (dX/dT,dY/dT).
+
+    INPUT PARAMETERS:
+        P   -   parametric spline interpolant
+        T   -   point:
+                * T in [0,1] corresponds to interval spanned by points
+                * for non-periodic splines T<0 (or T>1) correspond to parts of
+                  the curve before the first (after the last) point
+                * for periodic splines T<0 (or T>1) are projected  into  [0,1]
+                  by making T=T-floor(T).
+
+    OUTPUT PARAMETERS:
+        X   -   X-value
+        DX  -   X-derivative
+        Y   -   Y-value
+        DY  -   Y-derivative
+
+
+      -- ALGLIB PROJECT --
+         Copyright 28.05.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void pspline2diff(pspline2interpolant p, double t, out double x, out double dx, out double y, out double dy)
+    {
+        x = 0;
+        dx = 0;
+        y = 0;
+        dy = 0;
+        pspline.pspline2diff(p.innerobj, t, ref x, ref dx, ref y, ref dy);
+        return;
+    }
+
+    /*************************************************************************
+    This function calculates derivative, i.e. it returns (dX/dT,dY/dT,dZ/dT).
+
+    INPUT PARAMETERS:
+        P   -   parametric spline interpolant
+        T   -   point:
+                * T in [0,1] corresponds to interval spanned by points
+                * for non-periodic splines T<0 (or T>1) correspond to parts of
+                  the curve before the first (after the last) point
+                * for periodic splines T<0 (or T>1) are projected  into  [0,1]
+                  by making T=T-floor(T).
+
+    OUTPUT PARAMETERS:
+        X   -   X-value
+        DX  -   X-derivative
+        Y   -   Y-value
+        DY  -   Y-derivative
+        Z   -   Z-value
+        DZ  -   Z-derivative
+
+
+      -- ALGLIB PROJECT --
+         Copyright 28.05.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void pspline3diff(pspline3interpolant p, double t, out double x, out double dx, out double y, out double dy, out double z, out double dz)
+    {
+        x = 0;
+        dx = 0;
+        y = 0;
+        dy = 0;
+        z = 0;
+        dz = 0;
+        pspline.pspline3diff(p.innerobj, t, ref x, ref dx, ref y, ref dy, ref z, ref dz);
+        return;
+    }
+
+    /*************************************************************************
+    This function calculates first and second derivative with respect to T.
+
+    INPUT PARAMETERS:
+        P   -   parametric spline interpolant
+        T   -   point:
+                * T in [0,1] corresponds to interval spanned by points
+                * for non-periodic splines T<0 (or T>1) correspond to parts of
+                  the curve before the first (after the last) point
+                * for periodic splines T<0 (or T>1) are projected  into  [0,1]
+                  by making T=T-floor(T).
+
+    OUTPUT PARAMETERS:
+        X   -   X-value
+        DX  -   derivative
+        D2X -   second derivative
+        Y   -   Y-value
+        DY  -   derivative
+        D2Y -   second derivative
+
+
+      -- ALGLIB PROJECT --
+         Copyright 28.05.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void pspline2diff2(pspline2interpolant p, double t, out double x, out double dx, out double d2x, out double y, out double dy, out double d2y)
+    {
+        x = 0;
+        dx = 0;
+        d2x = 0;
+        y = 0;
+        dy = 0;
+        d2y = 0;
+        pspline.pspline2diff2(p.innerobj, t, ref x, ref dx, ref d2x, ref y, ref dy, ref d2y);
+        return;
+    }
+
+    /*************************************************************************
+    This function calculates first and second derivative with respect to T.
+
+    INPUT PARAMETERS:
+        P   -   parametric spline interpolant
+        T   -   point:
+                * T in [0,1] corresponds to interval spanned by points
+                * for non-periodic splines T<0 (or T>1) correspond to parts of
+                  the curve before the first (after the last) point
+                * for periodic splines T<0 (or T>1) are projected  into  [0,1]
+                  by making T=T-floor(T).
+
+    OUTPUT PARAMETERS:
+        X   -   X-value
+        DX  -   derivative
+        D2X -   second derivative
+        Y   -   Y-value
+        DY  -   derivative
+        D2Y -   second derivative
+        Z   -   Z-value
+        DZ  -   derivative
+        D2Z -   second derivative
+
+
+      -- ALGLIB PROJECT --
+         Copyright 28.05.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void pspline3diff2(pspline3interpolant p, double t, out double x, out double dx, out double d2x, out double y, out double dy, out double d2y, out double z, out double dz, out double d2z)
+    {
+        x = 0;
+        dx = 0;
+        d2x = 0;
+        y = 0;
+        dy = 0;
+        d2y = 0;
+        z = 0;
+        dz = 0;
+        d2z = 0;
+        pspline.pspline3diff2(p.innerobj, t, ref x, ref dx, ref d2x, ref y, ref dy, ref d2y, ref z, ref dz, ref d2z);
+        return;
+    }
+
+    /*************************************************************************
+    This function  calculates  arc length, i.e. length of  curve  between  t=a
+    and t=b.
+
+    INPUT PARAMETERS:
+        P   -   parametric spline interpolant
+        A,B -   parameter values corresponding to arc ends:
+                * B>A will result in positive length returned
+                * B<A will result in negative length returned
+
+    RESULT:
+        length of arc starting at T=A and ending at T=B.
+
+
+      -- ALGLIB PROJECT --
+         Copyright 30.05.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static double pspline2arclength(pspline2interpolant p, double a, double b)
+    {
+
+        double result = pspline.pspline2arclength(p.innerobj, a, b);
+        return result;
+    }
+
+    /*************************************************************************
+    This function  calculates  arc length, i.e. length of  curve  between  t=a
+    and t=b.
+
+    INPUT PARAMETERS:
+        P   -   parametric spline interpolant
+        A,B -   parameter values corresponding to arc ends:
+                * B>A will result in positive length returned
+                * B<A will result in negative length returned
+
+    RESULT:
+        length of arc starting at T=A and ending at T=B.
+
+
+      -- ALGLIB PROJECT --
+         Copyright 30.05.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static double pspline3arclength(pspline3interpolant p, double a, double b)
+    {
+
+        double result = pspline.pspline3arclength(p.innerobj, a, b);
+        return result;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    RBF model.
+
+    Never try to directly work with fields of this object - always use  ALGLIB
+    functions to use this object.
+    *************************************************************************/
+    public class rbfmodel
+    {
+        //
+        // Public declarations
+        //
+
+        public rbfmodel()
+        {
+            _innerobj = new rbf.rbfmodel();
+        }
+
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private rbf.rbfmodel _innerobj;
+        public rbf.rbfmodel innerobj { get { return _innerobj; } }
+        public rbfmodel(rbf.rbfmodel obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+
+    /*************************************************************************
+    RBF solution report:
+    * TerminationType   -   termination type, positive values - success,
+                            non-positive - failure.
+    *************************************************************************/
+    public class rbfreport
+    {
+        //
+        // Public declarations
+        //
+        public int arows { get { return _innerobj.arows; } set { _innerobj.arows = value; } }
+        public int acols { get { return _innerobj.acols; } set { _innerobj.acols = value; } }
+        public int annz { get { return _innerobj.annz; } set { _innerobj.annz = value; } }
+        public int iterationscount { get { return _innerobj.iterationscount; } set { _innerobj.iterationscount = value; } }
+        public int nmv { get { return _innerobj.nmv; } set { _innerobj.nmv = value; } }
+        public int terminationtype { get { return _innerobj.terminationtype; } set { _innerobj.terminationtype = value; } }
+
+        public rbfreport()
+        {
+            _innerobj = new rbf.rbfreport();
+        }
+
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private rbf.rbfreport _innerobj;
+        public rbf.rbfreport innerobj { get { return _innerobj; } }
+        public rbfreport(rbf.rbfreport obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+
+    /*************************************************************************
+    This function serializes data structure to string.
+
+    Important properties of s_out:
+    * it contains alphanumeric characters, dots, underscores, minus signs
+    * these symbols are grouped into words, which are separated by spaces
+      and Windows-style (CR+LF) newlines
+    * although  serializer  uses  spaces and CR+LF as separators, you can 
+      replace any separator character by arbitrary combination of spaces,
+      tabs, Windows or Unix newlines. It allows flexible reformatting  of
+      the  string  in  case you want to include it into text or XML file. 
+      But you should not insert separators into the middle of the "words"
+      nor you should change case of letters.
+    * s_out can be freely moved between 32-bit and 64-bit systems, little
+      and big endian machines, and so on. You can serialize structure  on
+      32-bit machine and unserialize it on 64-bit one (or vice versa), or
+      serialize  it  on  SPARC  and  unserialize  on  x86.  You  can also 
+      serialize  it  in  C# version of ALGLIB and unserialize in C++ one, 
+      and vice versa.
+    *************************************************************************/
+    public static void rbfserialize(rbfmodel obj, out string s_out)
+    {
+        alglib.serializer s = new alglib.serializer();
+        s.alloc_start();
+        rbf.rbfalloc(s, obj.innerobj);
+        s.sstart_str();
+        rbf.rbfserialize(s, obj.innerobj);
+        s.stop();
+        s_out = s.get_string();
+    }
+
+
+    /*************************************************************************
+    This function unserializes data structure from string.
+    *************************************************************************/
+    public static void rbfunserialize(string s_in, out rbfmodel obj)
+    {
+        alglib.serializer s = new alglib.serializer();
+        obj = new rbfmodel();
+        s.ustart_str(s_in);
+        rbf.rbfunserialize(s, obj.innerobj);
+        s.stop();
+    }
+
+    /*************************************************************************
+    This function creates RBF  model  for  a  scalar (NY=1)  or  vector (NY>1)
+    function in a NX-dimensional space (NX=2 or NX=3).
+
+    Newly created model is empty. It can be used for interpolation right after
+    creation, but it just returns zeros. You have to add points to the  model,
+    tune interpolation settings, and then  call  model  construction  function
+    RBFBuildModel() which will update model according to your specification.
+
+    USAGE:
+    1. User creates model with RBFCreate()
+    2. User adds dataset with RBFSetPoints() (points do NOT have to  be  on  a
+       regular grid)
+    3. (OPTIONAL) User chooses polynomial term by calling:
+       * RBFLinTerm() to set linear term
+       * RBFConstTerm() to set constant term
+       * RBFZeroTerm() to set zero term
+       By default, linear term is used.
+    4. User chooses specific RBF algorithm to use: either QNN (RBFSetAlgoQNN)
+       or ML (RBFSetAlgoMultiLayer).
+    5. User calls RBFBuildModel() function which rebuilds model  according  to
+       the specification
+    6. User may call RBFCalc() to calculate model value at the specified point,
+       RBFGridCalc() to  calculate   model  values at the points of the regular
+       grid. User may extract model coefficients with RBFUnpack() call.
+
+    INPUT PARAMETERS:
+        NX      -   dimension of the space, NX=2 or NX=3
+        NY      -   function dimension, NY>=1
+
+    OUTPUT PARAMETERS:
+        S       -   RBF model (initially equals to zero)
+
+    NOTE 1: memory requirements. RBF models require amount of memory  which is
+            proportional  to  the  number  of data points. Memory is allocated
+            during model construction, but most of this memory is freed  after
+            model coefficients are calculated.
+
+            Some approximate estimates for N centers with default settings are
+            given below:
+            * about 250*N*(sizeof(double)+2*sizeof(int)) bytes  of  memory  is
+              needed during model construction stage.
+            * about 15*N*sizeof(double) bytes is needed after model is built.
+            For example, for N=100000 we may need 0.6 GB of memory  to  build
+            model, but just about 0.012 GB to store it.
+
+      -- ALGLIB --
+         Copyright 13.12.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void rbfcreate(int nx, int ny, out rbfmodel s)
+    {
+        s = new rbfmodel();
+        rbf.rbfcreate(nx, ny, s.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    This function adds dataset.
+
+    This function overrides results of the previous calls, i.e. multiple calls
+    of this function will result in only the last set being added.
+
+    INPUT PARAMETERS:
+        S       -   RBF model, initialized by RBFCreate() call.
+        XY      -   points, array[N,NX+NY]. One row corresponds to  one  point
+                    in the dataset. First NX elements  are  coordinates,  next
+                    NY elements are function values. Array may  be larger than
+                    specific,  in  this  case  only leading [N,NX+NY] elements
+                    will be used.
+        N       -   number of points in the dataset
+
+    After you've added dataset and (optionally) tuned algorithm  settings  you
+    should call RBFBuildModel() in order to build a model for you.
+
+    NOTE: this   function  has   some   serialization-related  subtleties.  We
+          recommend you to study serialization examples from ALGLIB  Reference
+          Manual if you want to perform serialization of your models.
+
+
+      -- ALGLIB --
+         Copyright 13.12.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void rbfsetpoints(rbfmodel s, double[,] xy, int n)
+    {
+
+        rbf.rbfsetpoints(s.innerobj, xy, n);
+        return;
+    }
+    public static void rbfsetpoints(rbfmodel s, double[,] xy)
+    {
+        int n;
+
+
+        n = ap.rows(xy);
+        rbf.rbfsetpoints(s.innerobj, xy, n);
+
+        return;
+    }
+
+    /*************************************************************************
+    This  function  sets  RBF interpolation algorithm. ALGLIB supports several
+    RBF algorithms with different properties.
+
+    This algorithm is called RBF-QNN and  it  is  good  for  point  sets  with
+    following properties:
+    a) all points are distinct
+    b) all points are well separated.
+    c) points  distribution  is  approximately  uniform.  There is no "contour
+       lines", clusters of points, or other small-scale structures.
+
+    Algorithm description:
+    1) interpolation centers are allocated to data points
+    2) interpolation radii are calculated as distances to the  nearest centers
+       times Q coefficient (where Q is a value from [0.75,1.50]).
+    3) after  performing (2) radii are transformed in order to avoid situation
+       when single outlier has very large radius and  influences  many  points
+       across all dataset. Transformation has following form:
+           new_r[i] = min(r[i],Z*median(r[]))
+       where r[i] is I-th radius, median()  is a median  radius across  entire
+       dataset, Z is user-specified value which controls amount  of  deviation
+       from median radius.
+
+    When (a) is violated,  we  will  be unable to build RBF model. When (b) or
+    (c) are violated, model will be built, but interpolation quality  will  be
+    low. See http://www.alglib.net/interpolation/ for more information on this
+    subject.
+
+    This algorithm is used by default.
+
+    Additional Q parameter controls smoothness properties of the RBF basis:
+    * Q<0.75 will give perfectly conditioned basis,  but  terrible  smoothness
+      properties (RBF interpolant will have sharp peaks around function values)
+    * Q around 1.0 gives good balance between smoothness and condition number
+    * Q>1.5 will lead to badly conditioned systems and slow convergence of the
+      underlying linear solver (although smoothness will be very good)
+    * Q>2.0 will effectively make optimizer useless because it won't  converge
+      within reasonable amount of iterations. It is possible to set such large
+      Q, but it is advised not to do so.
+
+    INPUT PARAMETERS:
+        S       -   RBF model, initialized by RBFCreate() call
+        Q       -   Q parameter, Q>0, recommended value - 1.0
+        Z       -   Z parameter, Z>0, recommended value - 5.0
+
+    NOTE: this   function  has   some   serialization-related  subtleties.  We
+          recommend you to study serialization examples from ALGLIB  Reference
+          Manual if you want to perform serialization of your models.
+
+
+      -- ALGLIB --
+         Copyright 13.12.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void rbfsetalgoqnn(rbfmodel s, double q, double z)
+    {
+
+        rbf.rbfsetalgoqnn(s.innerobj, q, z);
+        return;
+    }
+    public static void rbfsetalgoqnn(rbfmodel s)
+    {
+        double q;
+        double z;
+
+
+        q = 1.0;
+        z = 5.0;
+        rbf.rbfsetalgoqnn(s.innerobj, q, z);
+
+        return;
+    }
+
+    /*************************************************************************
+    This  function  sets  RBF interpolation algorithm. ALGLIB supports several
+    RBF algorithms with different properties.
+
+    This  algorithm is called RBF-ML. It builds  multilayer  RBF  model,  i.e.
+    model with subsequently decreasing  radii,  which  allows  us  to  combine
+    smoothness (due to  large radii of  the first layers) with  exactness (due
+    to small radii of the last layers) and fast convergence.
+
+    Internally RBF-ML uses many different  means  of acceleration, from sparse
+    matrices  to  KD-trees,  which  results in algorithm whose working time is
+    roughly proportional to N*log(N)*Density*RBase^2*NLayers,  where  N  is  a
+    number of points, Density is an average density if points per unit of  the
+    interpolation space, RBase is an initial radius, NLayers is  a  number  of
+    layers.
+
+    RBF-ML is good for following kinds of interpolation problems:
+    1. "exact" problems (perfect fit) with well separated points
+    2. least squares problems with arbitrary distribution of points (algorithm
+       gives  perfect  fit  where it is possible, and resorts to least squares
+       fit in the hard areas).
+    3. noisy problems where  we  want  to  apply  some  controlled  amount  of
+       smoothing.
+
+    INPUT PARAMETERS:
+        S       -   RBF model, initialized by RBFCreate() call
+        RBase   -   RBase parameter, RBase>0
+        NLayers -   NLayers parameter, NLayers>0, recommended value  to  start
+                    with - about 5.
+        LambdaV -   regularization value, can be useful when  solving  problem
+                    in the least squares sense.  Optimal  lambda  is  problem-
+                    dependent and require trial and error. In our  experience,
+                    good lambda can be as large as 0.1, and you can use  0.001
+                    as initial guess.
+                    Default  value  - 0.01, which is used when LambdaV is  not
+                    given.  You  can  specify  zero  value,  but  it  is   not
+                    recommended to do so.
+
+    TUNING ALGORITHM
+
+    In order to use this algorithm you have to choose three parameters:
+    * initial radius RBase
+    * number of layers in the model NLayers
+    * regularization coefficient LambdaV
+
+    Initial radius is easy to choose - you can pick any number  several  times
+    larger  than  the  average  distance between points. Algorithm won't break
+    down if you choose radius which is too large (model construction time will
+    increase, but model will be built correctly).
+
+    Choose such number of layers that RLast=RBase/2^(NLayers-1)  (radius  used
+    by  the  last  layer)  will  be  smaller than the typical distance between
+    points.  In  case  model  error  is  too large, you can increase number of
+    layers.  Having  more  layers  will make model construction and evaluation
+    proportionally slower, but it will allow you to have model which precisely
+    fits your data. From the other side, if you want to  suppress  noise,  you
+    can DECREASE number of layers to make your model less flexible.
+
+    Regularization coefficient LambdaV controls smoothness of  the  individual
+    models built for each layer. We recommend you to use default value in case
+    you don't want to tune this parameter,  because  having  non-zero  LambdaV
+    accelerates and stabilizes internal iterative algorithm. In case you  want
+    to suppress noise you can use  LambdaV  as  additional  parameter  (larger
+    value = more smoothness) to tune.
+
+    TYPICAL ERRORS
+
+    1. Using  initial  radius  which is too large. Memory requirements  of the
+       RBF-ML are roughly proportional to N*Density*RBase^2 (where Density  is
+       an average density of points per unit of the interpolation  space).  In
+       the extreme case of the very large RBase we will need O(N^2)  units  of
+       memory - and many layers in order to decrease radius to some reasonably
+       small value.
+
+    2. Using too small number of layers - RBF models with large radius are not
+       flexible enough to reproduce small variations in the  target  function.
+       You  need  many  layers  with  different radii, from large to small, in
+       order to have good model.
+
+    3. Using  initial  radius  which  is  too  small.  You will get model with
+       "holes" in the areas which are too far away from interpolation centers.
+       However, algorithm will work correctly (and quickly) in this case.
+
+    4. Using too many layers - you will get too large and too slow model. This
+       model  will  perfectly  reproduce  your function, but maybe you will be
+       able to achieve similar results with less layers (and less memory).
+
+      -- ALGLIB --
+         Copyright 02.03.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static void rbfsetalgomultilayer(rbfmodel s, double rbase, int nlayers, double lambdav)
+    {
+
+        rbf.rbfsetalgomultilayer(s.innerobj, rbase, nlayers, lambdav);
+        return;
+    }
+    public static void rbfsetalgomultilayer(rbfmodel s, double rbase, int nlayers)
+    {
+        double lambdav;
+
+
+        lambdav = 0.01;
+        rbf.rbfsetalgomultilayer(s.innerobj, rbase, nlayers, lambdav);
+
+        return;
+    }
+
+    /*************************************************************************
+    This function sets linear term (model is a sum of radial  basis  functions
+    plus linear polynomial). This function won't have effect until  next  call
+    to RBFBuildModel().
+
+    INPUT PARAMETERS:
+        S       -   RBF model, initialized by RBFCreate() call
+
+    NOTE: this   function  has   some   serialization-related  subtleties.  We
+          recommend you to study serialization examples from ALGLIB  Reference
+          Manual if you want to perform serialization of your models.
+
+      -- ALGLIB --
+         Copyright 13.12.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void rbfsetlinterm(rbfmodel s)
+    {
+
+        rbf.rbfsetlinterm(s.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    This function sets constant term (model is a sum of radial basis functions
+    plus constant).  This  function  won't  have  effect  until  next  call to
+    RBFBuildModel().
+
+    INPUT PARAMETERS:
+        S       -   RBF model, initialized by RBFCreate() call
+
+    NOTE: this   function  has   some   serialization-related  subtleties.  We
+          recommend you to study serialization examples from ALGLIB  Reference
+          Manual if you want to perform serialization of your models.
+
+      -- ALGLIB --
+         Copyright 13.12.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void rbfsetconstterm(rbfmodel s)
+    {
+
+        rbf.rbfsetconstterm(s.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    This  function  sets  zero  term (model is a sum of radial basis functions
+    without polynomial term). This function won't have effect until next  call
+    to RBFBuildModel().
+
+    INPUT PARAMETERS:
+        S       -   RBF model, initialized by RBFCreate() call
+
+    NOTE: this   function  has   some   serialization-related  subtleties.  We
+          recommend you to study serialization examples from ALGLIB  Reference
+          Manual if you want to perform serialization of your models.
+
+      -- ALGLIB --
+         Copyright 13.12.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void rbfsetzeroterm(rbfmodel s)
+    {
+
+        rbf.rbfsetzeroterm(s.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    This   function  builds  RBF  model  and  returns  report  (contains  some
+    information which can be used for evaluation of the algorithm properties).
+
+    Call to this function modifies RBF model by calculating its centers/radii/
+    weights  and  saving  them  into  RBFModel  structure.  Initially RBFModel
+    contain zero coefficients, but after call to this function  we  will  have
+    coefficients which were calculated in order to fit our dataset.
+
+    After you called this function you can call RBFCalc(),  RBFGridCalc()  and
+    other model calculation functions.
+
+    INPUT PARAMETERS:
+        S       -   RBF model, initialized by RBFCreate() call
+        Rep     -   report:
+                    * Rep.TerminationType:
+                      * -5 - non-distinct basis function centers were detected,
+                             interpolation aborted
+                      * -4 - nonconvergence of the internal SVD solver
+                      *  1 - successful termination
+                    Fields are used for debugging purposes:
+                    * Rep.IterationsCount - iterations count of the LSQR solver
+                    * Rep.NMV - number of matrix-vector products
+                    * Rep.ARows - rows count for the system matrix
+                    * Rep.ACols - columns count for the system matrix
+                    * Rep.ANNZ - number of significantly non-zero elements
+                      (elements above some algorithm-determined threshold)
+
+    NOTE:  failure  to  build  model will leave current state of the structure
+    unchanged.
+
+      -- ALGLIB --
+         Copyright 13.12.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void rbfbuildmodel(rbfmodel s, out rbfreport rep)
+    {
+        rep = new rbfreport();
+        rbf.rbfbuildmodel(s.innerobj, rep.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    This function calculates values of the RBF model in the given point.
+
+    This function should be used when we have NY=1 (scalar function) and  NX=2
+    (2-dimensional space). If you have 3-dimensional space, use RBFCalc3(). If
+    you have general situation (NX-dimensional space, NY-dimensional function)
+    you should use general, less efficient implementation RBFCalc().
+
+    If  you  want  to  calculate  function  values  many times, consider using
+    RBFGridCalc2(), which is far more efficient than many subsequent calls  to
+    RBFCalc2().
+
+    This function returns 0.0 when:
+    * model is not initialized
+    * NX<>2
+     *NY<>1
+
+    INPUT PARAMETERS:
+        S       -   RBF model
+        X0      -   first coordinate, finite number
+        X1      -   second coordinate, finite number
+
+    RESULT:
+        value of the model or 0.0 (as defined above)
+
+      -- ALGLIB --
+         Copyright 13.12.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static double rbfcalc2(rbfmodel s, double x0, double x1)
+    {
+
+        double result = rbf.rbfcalc2(s.innerobj, x0, x1);
+        return result;
+    }
+
+    /*************************************************************************
+    This function calculates values of the RBF model in the given point.
+
+    This function should be used when we have NY=1 (scalar function) and  NX=3
+    (3-dimensional space). If you have 2-dimensional space, use RBFCalc2(). If
+    you have general situation (NX-dimensional space, NY-dimensional function)
+    you should use general, less efficient implementation RBFCalc().
+
+    This function returns 0.0 when:
+    * model is not initialized
+    * NX<>3
+     *NY<>1
+
+    INPUT PARAMETERS:
+        S       -   RBF model
+        X0      -   first coordinate, finite number
+        X1      -   second coordinate, finite number
+        X2      -   third coordinate, finite number
+
+    RESULT:
+        value of the model or 0.0 (as defined above)
+
+      -- ALGLIB --
+         Copyright 13.12.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static double rbfcalc3(rbfmodel s, double x0, double x1, double x2)
+    {
+
+        double result = rbf.rbfcalc3(s.innerobj, x0, x1, x2);
+        return result;
+    }
+
+    /*************************************************************************
+    This function calculates values of the RBF model at the given point.
+
+    This is general function which can be used for arbitrary NX (dimension  of
+    the space of arguments) and NY (dimension of the function itself). However
+    when  you  have  NY=1  you  may  find more convenient to use RBFCalc2() or
+    RBFCalc3().
+
+    This function returns 0.0 when model is not initialized.
+
+    INPUT PARAMETERS:
+        S       -   RBF model
+        X       -   coordinates, array[NX].
+                    X may have more than NX elements, in this case only
+                    leading NX will be used.
+
+    OUTPUT PARAMETERS:
+        Y       -   function value, array[NY]. Y is out-parameter and
+                    reallocated after call to this function. In case you  want
+                    to reuse previously allocated Y, you may use RBFCalcBuf(),
+                    which reallocates Y only when it is too small.
+
+      -- ALGLIB --
+         Copyright 13.12.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void rbfcalc(rbfmodel s, double[] x, out double[] y)
+    {
+        y = new double[0];
+        rbf.rbfcalc(s.innerobj, x, ref y);
+        return;
+    }
+
+    /*************************************************************************
+    This function calculates values of the RBF model at the given point.
+
+    Same as RBFCalc(), but does not reallocate Y when in is large enough to
+    store function values.
+
+    INPUT PARAMETERS:
+        S       -   RBF model
+        X       -   coordinates, array[NX].
+                    X may have more than NX elements, in this case only
+                    leading NX will be used.
+        Y       -   possibly preallocated array
+
+    OUTPUT PARAMETERS:
+        Y       -   function value, array[NY]. Y is not reallocated when it
+                    is larger than NY.
+
+      -- ALGLIB --
+         Copyright 13.12.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void rbfcalcbuf(rbfmodel s, double[] x, ref double[] y)
+    {
+
+        rbf.rbfcalcbuf(s.innerobj, x, ref y);
+        return;
+    }
+
+    /*************************************************************************
+    This function calculates values of the RBF model at the regular grid.
+
+    Grid have N0*N1 points, with Point[I,J] = (X0[I], X1[J])
+
+    This function returns 0.0 when:
+    * model is not initialized
+    * NX<>2
+     *NY<>1
+
+    INPUT PARAMETERS:
+        S       -   RBF model
+        X0      -   array of grid nodes, first coordinates, array[N0]
+        N0      -   grid size (number of nodes) in the first dimension
+        X1      -   array of grid nodes, second coordinates, array[N1]
+        N1      -   grid size (number of nodes) in the second dimension
+
+    OUTPUT PARAMETERS:
+        Y       -   function values, array[N0,N1]. Y is out-variable and
+                    is reallocated by this function.
+
+      -- ALGLIB --
+         Copyright 13.12.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void rbfgridcalc2(rbfmodel s, double[] x0, int n0, double[] x1, int n1, out double[,] y)
+    {
+        y = new double[0,0];
+        rbf.rbfgridcalc2(s.innerobj, x0, n0, x1, n1, ref y);
+        return;
+    }
+
+    /*************************************************************************
+    This function "unpacks" RBF model by extracting its coefficients.
+
+    INPUT PARAMETERS:
+        S       -   RBF model
+
+    OUTPUT PARAMETERS:
+        NX      -   dimensionality of argument
+        NY      -   dimensionality of the target function
+        XWR     -   model information, array[NC,NX+NY+1].
+                    One row of the array corresponds to one basis function:
+                    * first NX columns  - coordinates of the center
+                    * next NY columns   - weights, one per dimension of the
+                                          function being modelled
+                    * last column       - radius, same for all dimensions of
+                                          the function being modelled
+        NC      -   number of the centers
+        V       -   polynomial  term , array[NY,NX+1]. One row per one
+                    dimension of the function being modelled. First NX
+                    elements are linear coefficients, V[NX] is equal to the
+                    constant part.
+
+      -- ALGLIB --
+         Copyright 13.12.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void rbfunpack(rbfmodel s, out int nx, out int ny, out double[,] xwr, out int nc, out double[,] v)
+    {
+        nx = 0;
+        ny = 0;
+        xwr = new double[0,0];
+        nc = 0;
+        v = new double[0,0];
+        rbf.rbfunpack(s.innerobj, ref nx, ref ny, ref xwr, ref nc, ref v);
+        return;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    2-dimensional spline inteprolant
+    *************************************************************************/
+    public class spline2dinterpolant
+    {
+        //
+        // Public declarations
+        //
+
+        public spline2dinterpolant()
+        {
+            _innerobj = new spline2d.spline2dinterpolant();
+        }
+
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private spline2d.spline2dinterpolant _innerobj;
+        public spline2d.spline2dinterpolant innerobj { get { return _innerobj; } }
+        public spline2dinterpolant(spline2d.spline2dinterpolant obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+    /*************************************************************************
+    This subroutine builds bilinear spline coefficients table.
+
+    Input parameters:
+        X   -   spline abscissas, array[0..N-1]
+        Y   -   spline ordinates, array[0..M-1]
+        F   -   function values, array[0..M-1,0..N-1]
+        M,N -   grid size, M>=2, N>=2
+
+    Output parameters:
+        C   -   spline interpolant
+
+      -- ALGLIB PROJECT --
+         Copyright 05.07.2007 by Bochkanov Sergey
+    *************************************************************************/
+    public static void spline2dbuildbilinear(double[] x, double[] y, double[,] f, int m, int n, out spline2dinterpolant c)
+    {
+        c = new spline2dinterpolant();
+        spline2d.spline2dbuildbilinear(x, y, f, m, n, c.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    This subroutine builds bicubic spline coefficients table.
+
+    Input parameters:
+        X   -   spline abscissas, array[0..N-1]
+        Y   -   spline ordinates, array[0..M-1]
+        F   -   function values, array[0..M-1,0..N-1]
+        M,N -   grid size, M>=2, N>=2
+
+    Output parameters:
+        C   -   spline interpolant
+
+      -- ALGLIB PROJECT --
+         Copyright 05.07.2007 by Bochkanov Sergey
+    *************************************************************************/
+    public static void spline2dbuildbicubic(double[] x, double[] y, double[,] f, int m, int n, out spline2dinterpolant c)
+    {
+        c = new spline2dinterpolant();
+        spline2d.spline2dbuildbicubic(x, y, f, m, n, c.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    This subroutine calculates the value of the bilinear or bicubic spline  at
+    the given point X.
+
+    Input parameters:
+        C   -   coefficients table.
+                Built by BuildBilinearSpline or BuildBicubicSpline.
+        X, Y-   point
+
+    Result:
+        S(x,y)
+
+      -- ALGLIB PROJECT --
+         Copyright 05.07.2007 by Bochkanov Sergey
+    *************************************************************************/
+    public static double spline2dcalc(spline2dinterpolant c, double x, double y)
+    {
+
+        double result = spline2d.spline2dcalc(c.innerobj, x, y);
+        return result;
+    }
+
+    /*************************************************************************
+    This subroutine calculates the value of the bilinear or bicubic spline  at
+    the given point X and its derivatives.
+
+    Input parameters:
+        C   -   spline interpolant.
+        X, Y-   point
+
+    Output parameters:
+        F   -   S(x,y)
+        FX  -   dS(x,y)/dX
+        FY  -   dS(x,y)/dY
+        FXY -   d2S(x,y)/dXdY
+
+      -- ALGLIB PROJECT --
+         Copyright 05.07.2007 by Bochkanov Sergey
+    *************************************************************************/
+    public static void spline2ddiff(spline2dinterpolant c, double x, double y, out double f, out double fx, out double fy, out double fxy)
+    {
+        f = 0;
+        fx = 0;
+        fy = 0;
+        fxy = 0;
+        spline2d.spline2ddiff(c.innerobj, x, y, ref f, ref fx, ref fy, ref fxy);
+        return;
+    }
+
+    /*************************************************************************
+    This subroutine unpacks two-dimensional spline into the coefficients table
+
+    Input parameters:
+        C   -   spline interpolant.
+
+    Result:
+        M, N-   grid size (x-axis and y-axis)
+        Tbl -   coefficients table, unpacked format,
+                [0..(N-1)*(M-1)-1, 0..19].
+                For I = 0...M-2, J=0..N-2:
+                    K =  I*(N-1)+J
+                    Tbl[K,0] = X[j]
+                    Tbl[K,1] = X[j+1]
+                    Tbl[K,2] = Y[i]
+                    Tbl[K,3] = Y[i+1]
+                    Tbl[K,4] = C00
+                    Tbl[K,5] = C01
+                    Tbl[K,6] = C02
+                    Tbl[K,7] = C03
+                    Tbl[K,8] = C10
+                    Tbl[K,9] = C11
+                    ...
+                    Tbl[K,19] = C33
+                On each grid square spline is equals to:
+                    S(x) = SUM(c[i,j]*(x^i)*(y^j), i=0..3, j=0..3)
+                    t = x-x[j]
+                    u = y-y[i]
+
+      -- ALGLIB PROJECT --
+         Copyright 29.06.2007 by Bochkanov Sergey
+    *************************************************************************/
+    public static void spline2dunpack(spline2dinterpolant c, out int m, out int n, out double[,] tbl)
+    {
+        m = 0;
+        n = 0;
+        tbl = new double[0,0];
+        spline2d.spline2dunpack(c.innerobj, ref m, ref n, ref tbl);
+        return;
+    }
+
+    /*************************************************************************
+    This subroutine performs linear transformation of the spline argument.
+
+    Input parameters:
+        C       -   spline interpolant
+        AX, BX  -   transformation coefficients: x = A*t + B
+        AY, BY  -   transformation coefficients: y = A*u + B
+    Result:
+        C   -   transformed spline
+
+      -- ALGLIB PROJECT --
+         Copyright 30.06.2007 by Bochkanov Sergey
+    *************************************************************************/
+    public static void spline2dlintransxy(spline2dinterpolant c, double ax, double bx, double ay, double by)
+    {
+
+        spline2d.spline2dlintransxy(c.innerobj, ax, bx, ay, by);
+        return;
+    }
+
+    /*************************************************************************
+    This subroutine performs linear transformation of the spline.
+
+    Input parameters:
+        C   -   spline interpolant.
+        A, B-   transformation coefficients: S2(x,y) = A*S(x,y) + B
+
+    Output parameters:
+        C   -   transformed spline
+
+      -- ALGLIB PROJECT --
+         Copyright 30.06.2007 by Bochkanov Sergey
+    *************************************************************************/
+    public static void spline2dlintransf(spline2dinterpolant c, double a, double b)
+    {
+
+        spline2d.spline2dlintransf(c.innerobj, a, b);
+        return;
+    }
+
+    /*************************************************************************
+    Bicubic spline resampling
+
+    Input parameters:
+        A           -   function values at the old grid,
+                        array[0..OldHeight-1, 0..OldWidth-1]
+        OldHeight   -   old grid height, OldHeight>1
+        OldWidth    -   old grid width, OldWidth>1
+        NewHeight   -   new grid height, NewHeight>1
+        NewWidth    -   new grid width, NewWidth>1
+
+    Output parameters:
+        B           -   function values at the new grid,
+                        array[0..NewHeight-1, 0..NewWidth-1]
+
+      -- ALGLIB routine --
+         15 May, 2007
+         Copyright by Bochkanov Sergey
+    *************************************************************************/
+    public static void spline2dresamplebicubic(double[,] a, int oldheight, int oldwidth, out double[,] b, int newheight, int newwidth)
+    {
+        b = new double[0,0];
+        spline2d.spline2dresamplebicubic(a, oldheight, oldwidth, ref b, newheight, newwidth);
+        return;
+    }
+
+    /*************************************************************************
+    Bilinear spline resampling
+
+    Input parameters:
+        A           -   function values at the old grid,
+                        array[0..OldHeight-1, 0..OldWidth-1]
+        OldHeight   -   old grid height, OldHeight>1
+        OldWidth    -   old grid width, OldWidth>1
+        NewHeight   -   new grid height, NewHeight>1
+        NewWidth    -   new grid width, NewWidth>1
+
+    Output parameters:
+        B           -   function values at the new grid,
+                        array[0..NewHeight-1, 0..NewWidth-1]
+
+      -- ALGLIB routine --
+         09.07.2007
+         Copyright by Bochkanov Sergey
+    *************************************************************************/
+    public static void spline2dresamplebilinear(double[,] a, int oldheight, int oldwidth, out double[,] b, int newheight, int newwidth)
+    {
+        b = new double[0,0];
+        spline2d.spline2dresamplebilinear(a, oldheight, oldwidth, ref b, newheight, newwidth);
+        return;
+    }
+
+}
+public partial class alglib
+{
+    public class idwint
+    {
+        /*************************************************************************
+        IDW interpolant.
+        *************************************************************************/
+        public class idwinterpolant
+        {
+            public int n;
+            public int nx;
+            public int d;
+            public double r;
+            public int nw;
+            public nearestneighbor.kdtree tree;
+            public int modeltype;
+            public double[,] q;
+            public double[] xbuf;
+            public int[] tbuf;
+            public double[] rbuf;
+            public double[,] xybuf;
+            public int debugsolverfailures;
+            public double debugworstrcond;
+            public double debugbestrcond;
+            public idwinterpolant()
+            {
+                tree = new nearestneighbor.kdtree();
+                q = new double[0,0];
+                xbuf = new double[0];
+                tbuf = new int[0];
+                rbuf = new double[0];
+                xybuf = new double[0,0];
+            }
+        };
+
+
+
+
+        public const double idwqfactor = 1.5;
+        public const int idwkmin = 5;
+
+
+        /*************************************************************************
+        IDW interpolation
+
+        INPUT PARAMETERS:
+            Z   -   IDW interpolant built with one of model building
+                    subroutines.
+            X   -   array[0..NX-1], interpolation point
+
+        Result:
+            IDW interpolant Z(X)
+
+          -- ALGLIB --
+             Copyright 02.03.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static double idwcalc(idwinterpolant z,
+            double[] x)
+        {
+            double result = 0;
+            int nx = 0;
+            int i = 0;
+            int k = 0;
+            double r = 0;
+            double s = 0;
+            double w = 0;
+            double v1 = 0;
+            double v2 = 0;
+            double d0 = 0;
+            double di = 0;
+
+            
+            //
+            // these initializers are not really necessary,
+            // but without them compiler complains about uninitialized locals
+            //
+            k = 0;
+            
+            //
+            // Query
+            //
+            if( z.modeltype==0 )
+            {
+                
+                //
+                // NQ/NW-based model
+                //
+                nx = z.nx;
+                k = nearestneighbor.kdtreequeryknn(z.tree, x, z.nw, true);
+                nearestneighbor.kdtreequeryresultsdistances(z.tree, ref z.rbuf);
+                nearestneighbor.kdtreequeryresultstags(z.tree, ref z.tbuf);
+            }
+            if( z.modeltype==1 )
+            {
+                
+                //
+                // R-based model
+                //
+                nx = z.nx;
+                k = nearestneighbor.kdtreequeryrnn(z.tree, x, z.r, true);
+                nearestneighbor.kdtreequeryresultsdistances(z.tree, ref z.rbuf);
+                nearestneighbor.kdtreequeryresultstags(z.tree, ref z.tbuf);
+                if( k<idwkmin )
+                {
+                    
+                    //
+                    // we need at least IDWKMin points
+                    //
+                    k = nearestneighbor.kdtreequeryknn(z.tree, x, idwkmin, true);
+                    nearestneighbor.kdtreequeryresultsdistances(z.tree, ref z.rbuf);
+                    nearestneighbor.kdtreequeryresultstags(z.tree, ref z.tbuf);
+                }
+            }
+            
+            //
+            // initialize weights for linear/quadratic members calculation.
+            //
+            // NOTE 1: weights are calculated using NORMALIZED modified
+            // Shepard's formula. Original formula gives w(i) = sqr((R-di)/(R*di)),
+            // where di is i-th distance, R is max(di). Modified formula have
+            // following form:
+            //     w_mod(i) = 1, if di=d0
+            //     w_mod(i) = w(i)/w(0), if di<>d0
+            //
+            // NOTE 2: self-match is USED for this query
+            //
+            // NOTE 3: last point almost always gain zero weight, but it MUST
+            // be used for fitting because sometimes it will gain NON-ZERO
+            // weight - for example, when all distances are equal.
+            //
+            r = z.rbuf[k-1];
+            d0 = z.rbuf[0];
+            result = 0;
+            s = 0;
+            for(i=0; i<=k-1; i++)
+            {
+                di = z.rbuf[i];
+                if( (double)(di)==(double)(d0) )
+                {
+                    
+                    //
+                    // distance is equal to shortest, set it 1.0
+                    // without explicitly calculating (which would give
+                    // us same result, but 'll expose us to the risk of
+                    // division by zero).
+                    //
+                    w = 1;
+                }
+                else
+                {
+                    
+                    //
+                    // use normalized formula
+                    //
+                    v1 = (r-di)/(r-d0);
+                    v2 = d0/di;
+                    w = math.sqr(v1*v2);
+                }
+                result = result+w*idwcalcq(z, x, z.tbuf[i]);
+                s = s+w;
+            }
+            result = result/s;
+            return result;
+        }
+
+
+        /*************************************************************************
+        IDW interpolant using modified Shepard method for uniform point
+        distributions.
+
+        INPUT PARAMETERS:
+            XY  -   X and Y values, array[0..N-1,0..NX].
+                    First NX columns contain X-values, last column contain
+                    Y-values.
+            N   -   number of nodes, N>0.
+            NX  -   space dimension, NX>=1.
+            D   -   nodal function type, either:
+                    * 0     constant  model.  Just  for  demonstration only, worst
+                            model ever.
+                    * 1     linear model, least squares fitting. Simpe  model  for
+                            datasets too small for quadratic models
+                    * 2     quadratic  model,  least  squares  fitting. Best model
+                            available (if your dataset is large enough).
+                    * -1    "fast"  linear  model,  use  with  caution!!!   It  is
+                            significantly  faster than linear/quadratic and better
+                            than constant model. But it is less robust (especially
+                            in the presence of noise).
+            NQ  -   number of points used to calculate  nodal  functions  (ignored
+                    for constant models). NQ should be LARGER than:
+                    * max(1.5*(1+NX),2^NX+1) for linear model,
+                    * max(3/4*(NX+2)*(NX+1),2^NX+1) for quadratic model.
+                    Values less than this threshold will be silently increased.
+            NW  -   number of points used to calculate weights and to interpolate.
+                    Required: >=2^NX+1, values less than this  threshold  will  be
+                    silently increased.
+                    Recommended value: about 2*NQ
+
+        OUTPUT PARAMETERS:
+            Z   -   IDW interpolant.
+            
+        NOTES:
+          * best results are obtained with quadratic models, worst - with constant
+            models
+          * when N is large, NQ and NW must be significantly smaller than  N  both
+            to obtain optimal performance and to obtain optimal accuracy. In 2  or
+            3-dimensional tasks NQ=15 and NW=25 are good values to start with.
+          * NQ  and  NW  may  be  greater  than  N.  In  such  cases  they will be
+            automatically decreased.
+          * this subroutine is always succeeds (as long as correct parameters  are
+            passed).
+          * see  'Multivariate  Interpolation  of Large Sets of Scattered Data' by
+            Robert J. Renka for more information on this algorithm.
+          * this subroutine assumes that point distribution is uniform at the small
+            scales.  If  it  isn't  -  for  example,  points are concentrated along
+            "lines", but "lines" distribution is uniform at the larger scale - then
+            you should use IDWBuildModifiedShepardR()
+
+
+          -- ALGLIB PROJECT --
+             Copyright 02.03.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void idwbuildmodifiedshepard(double[,] xy,
+            int n,
+            int nx,
+            int d,
+            int nq,
+            int nw,
+            idwinterpolant z)
+        {
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int j2 = 0;
+            int j3 = 0;
+            double v = 0;
+            double r = 0;
+            double s = 0;
+            double d0 = 0;
+            double di = 0;
+            double v1 = 0;
+            double v2 = 0;
+            int nc = 0;
+            int offs = 0;
+            double[] x = new double[0];
+            double[] qrbuf = new double[0];
+            double[,] qxybuf = new double[0,0];
+            double[] y = new double[0];
+            double[,] fmatrix = new double[0,0];
+            double[] w = new double[0];
+            double[] qsol = new double[0];
+            double[] temp = new double[0];
+            int[] tags = new int[0];
+            int info = 0;
+            double taskrcond = 0;
+            int i_ = 0;
+
+            
+            //
+            // these initializers are not really necessary,
+            // but without them compiler complains about uninitialized locals
+            //
+            nc = 0;
+            
+            //
+            // assertions
+            //
+            alglib.ap.assert(n>0, "IDWBuildModifiedShepard: N<=0!");
+            alglib.ap.assert(nx>=1, "IDWBuildModifiedShepard: NX<1!");
+            alglib.ap.assert(d>=-1 && d<=2, "IDWBuildModifiedShepard: D<>-1 and D<>0 and D<>1 and D<>2!");
+            
+            //
+            // Correct parameters if needed
+            //
+            if( d==1 )
+            {
+                nq = Math.Max(nq, (int)Math.Ceiling(idwqfactor*(1+nx))+1);
+                nq = Math.Max(nq, (int)Math.Round(Math.Pow(2, nx))+1);
+            }
+            if( d==2 )
+            {
+                nq = Math.Max(nq, (int)Math.Ceiling(idwqfactor*(nx+2)*(nx+1)/2)+1);
+                nq = Math.Max(nq, (int)Math.Round(Math.Pow(2, nx))+1);
+            }
+            nw = Math.Max(nw, (int)Math.Round(Math.Pow(2, nx))+1);
+            nq = Math.Min(nq, n);
+            nw = Math.Min(nw, n);
+            
+            //
+            // primary initialization of Z
+            //
+            idwinit1(n, nx, d, nq, nw, z);
+            z.modeltype = 0;
+            
+            //
+            // Create KD-tree
+            //
+            tags = new int[n];
+            for(i=0; i<=n-1; i++)
+            {
+                tags[i] = i;
+            }
+            nearestneighbor.kdtreebuildtagged(xy, tags, n, nx, 1, 2, z.tree);
+            
+            //
+            // build nodal functions
+            //
+            temp = new double[nq+1];
+            x = new double[nx];
+            qrbuf = new double[nq];
+            qxybuf = new double[nq, nx+1];
+            if( d==-1 )
+            {
+                w = new double[nq];
+            }
+            if( d==1 )
+            {
+                y = new double[nq];
+                w = new double[nq];
+                qsol = new double[nx];
+                
+                //
+                // NX for linear members,
+                // 1 for temporary storage
+                //
+                fmatrix = new double[nq, nx+1];
+            }
+            if( d==2 )
+            {
+                y = new double[nq];
+                w = new double[nq];
+                qsol = new double[nx+(int)Math.Round(nx*(nx+1)*0.5)];
+                
+                //
+                // NX for linear members,
+                // Round(NX*(NX+1)*0.5) for quadratic model,
+                // 1 for temporary storage
+                //
+                fmatrix = new double[nq, nx+(int)Math.Round(nx*(nx+1)*0.5)+1];
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                
+                //
+                // Initialize center and function value.
+                // If D=0 it is all what we need
+                //
+                for(i_=0; i_<=nx;i_++)
+                {
+                    z.q[i,i_] = xy[i,i_];
+                }
+                if( d==0 )
+                {
+                    continue;
+                }
+                
+                //
+                // calculate weights for linear/quadratic members calculation.
+                //
+                // NOTE 1: weights are calculated using NORMALIZED modified
+                // Shepard's formula. Original formula is w(i) = sqr((R-di)/(R*di)),
+                // where di is i-th distance, R is max(di). Modified formula have
+                // following form:
+                //     w_mod(i) = 1, if di=d0
+                //     w_mod(i) = w(i)/w(0), if di<>d0
+                //
+                // NOTE 2: self-match is NOT used for this query
+                //
+                // NOTE 3: last point almost always gain zero weight, but it MUST
+                // be used for fitting because sometimes it will gain NON-ZERO
+                // weight - for example, when all distances are equal.
+                //
+                for(i_=0; i_<=nx-1;i_++)
+                {
+                    x[i_] = xy[i,i_];
+                }
+                k = nearestneighbor.kdtreequeryknn(z.tree, x, nq, false);
+                nearestneighbor.kdtreequeryresultsxy(z.tree, ref qxybuf);
+                nearestneighbor.kdtreequeryresultsdistances(z.tree, ref qrbuf);
+                r = qrbuf[k-1];
+                d0 = qrbuf[0];
+                for(j=0; j<=k-1; j++)
+                {
+                    di = qrbuf[j];
+                    if( (double)(di)==(double)(d0) )
+                    {
+                        
+                        //
+                        // distance is equal to shortest, set it 1.0
+                        // without explicitly calculating (which would give
+                        // us same result, but 'll expose us to the risk of
+                        // division by zero).
+                        //
+                        w[j] = 1;
+                    }
+                    else
+                    {
+                        
+                        //
+                        // use normalized formula
+                        //
+                        v1 = (r-di)/(r-d0);
+                        v2 = d0/di;
+                        w[j] = math.sqr(v1*v2);
+                    }
+                }
+                
+                //
+                // calculate linear/quadratic members
+                //
+                if( d==-1 )
+                {
+                    
+                    //
+                    // "Fast" linear nodal function calculated using
+                    // inverse distance weighting
+                    //
+                    for(j=0; j<=nx-1; j++)
+                    {
+                        x[j] = 0;
+                    }
+                    s = 0;
+                    for(j=0; j<=k-1; j++)
+                    {
+                        
+                        //
+                        // calculate J-th inverse distance weighted gradient:
+                        //     grad_k = (y_j-y_k)*(x_j-x_k)/sqr(norm(x_j-x_k))
+                        //     grad   = sum(wk*grad_k)/sum(w_k)
+                        //
+                        v = 0;
+                        for(j2=0; j2<=nx-1; j2++)
+                        {
+                            v = v+math.sqr(qxybuf[j,j2]-xy[i,j2]);
+                        }
+                        
+                        //
+                        // Although x_j<>x_k, sqr(norm(x_j-x_k)) may be zero due to
+                        // underflow. If it is, we assume than J-th gradient is zero
+                        // (i.e. don't add anything)
+                        //
+                        if( (double)(v)!=(double)(0) )
+                        {
+                            for(j2=0; j2<=nx-1; j2++)
+                            {
+                                x[j2] = x[j2]+w[j]*(qxybuf[j,nx]-xy[i,nx])*(qxybuf[j,j2]-xy[i,j2])/v;
+                            }
+                        }
+                        s = s+w[j];
+                    }
+                    for(j=0; j<=nx-1; j++)
+                    {
+                        z.q[i,nx+1+j] = x[j]/s;
+                    }
+                }
+                else
+                {
+                    
+                    //
+                    // Least squares models: build
+                    //
+                    if( d==1 )
+                    {
+                        
+                        //
+                        // Linear nodal function calculated using
+                        // least squares fitting to its neighbors
+                        //
+                        for(j=0; j<=k-1; j++)
+                        {
+                            for(j2=0; j2<=nx-1; j2++)
+                            {
+                                fmatrix[j,j2] = qxybuf[j,j2]-xy[i,j2];
+                            }
+                            y[j] = qxybuf[j,nx]-xy[i,nx];
+                        }
+                        nc = nx;
+                    }
+                    if( d==2 )
+                    {
+                        
+                        //
+                        // Quadratic nodal function calculated using
+                        // least squares fitting to its neighbors
+                        //
+                        for(j=0; j<=k-1; j++)
+                        {
+                            offs = 0;
+                            for(j2=0; j2<=nx-1; j2++)
+                            {
+                                fmatrix[j,offs] = qxybuf[j,j2]-xy[i,j2];
+                                offs = offs+1;
+                            }
+                            for(j2=0; j2<=nx-1; j2++)
+                            {
+                                for(j3=j2; j3<=nx-1; j3++)
+                                {
+                                    fmatrix[j,offs] = (qxybuf[j,j2]-xy[i,j2])*(qxybuf[j,j3]-xy[i,j3]);
+                                    offs = offs+1;
+                                }
+                            }
+                            y[j] = qxybuf[j,nx]-xy[i,nx];
+                        }
+                        nc = nx+(int)Math.Round(nx*(nx+1)*0.5);
+                    }
+                    idwinternalsolver(ref y, ref w, ref fmatrix, ref temp, k, nc, ref info, ref qsol, ref taskrcond);
+                    
+                    //
+                    // Least squares models: copy results
+                    //
+                    if( info>0 )
+                    {
+                        
+                        //
+                        // LLS task is solved, copy results
+                        //
+                        z.debugworstrcond = Math.Min(z.debugworstrcond, taskrcond);
+                        z.debugbestrcond = Math.Max(z.debugbestrcond, taskrcond);
+                        for(j=0; j<=nc-1; j++)
+                        {
+                            z.q[i,nx+1+j] = qsol[j];
+                        }
+                    }
+                    else
+                    {
+                        
+                        //
+                        // Solver failure, very strange, but we will use
+                        // zero values to handle it.
+                        //
+                        z.debugsolverfailures = z.debugsolverfailures+1;
+                        for(j=0; j<=nc-1; j++)
+                        {
+                            z.q[i,nx+1+j] = 0;
+                        }
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        IDW interpolant using modified Shepard method for non-uniform datasets.
+
+        This type of model uses  constant  nodal  functions and interpolates using
+        all nodes which are closer than user-specified radius R. It  may  be  used
+        when points distribution is non-uniform at the small scale, but it  is  at
+        the distances as large as R.
+
+        INPUT PARAMETERS:
+            XY  -   X and Y values, array[0..N-1,0..NX].
+                    First NX columns contain X-values, last column contain
+                    Y-values.
+            N   -   number of nodes, N>0.
+            NX  -   space dimension, NX>=1.
+            R   -   radius, R>0
+
+        OUTPUT PARAMETERS:
+            Z   -   IDW interpolant.
+
+        NOTES:
+        * if there is less than IDWKMin points within  R-ball,  algorithm  selects
+          IDWKMin closest ones, so that continuity properties of  interpolant  are
+          preserved even far from points.
+
+          -- ALGLIB PROJECT --
+             Copyright 11.04.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void idwbuildmodifiedshepardr(double[,] xy,
+            int n,
+            int nx,
+            double r,
+            idwinterpolant z)
+        {
+            int i = 0;
+            int[] tags = new int[0];
+            int i_ = 0;
+
+            
+            //
+            // assertions
+            //
+            alglib.ap.assert(n>0, "IDWBuildModifiedShepardR: N<=0!");
+            alglib.ap.assert(nx>=1, "IDWBuildModifiedShepardR: NX<1!");
+            alglib.ap.assert((double)(r)>(double)(0), "IDWBuildModifiedShepardR: R<=0!");
+            
+            //
+            // primary initialization of Z
+            //
+            idwinit1(n, nx, 0, 0, n, z);
+            z.modeltype = 1;
+            z.r = r;
+            
+            //
+            // Create KD-tree
+            //
+            tags = new int[n];
+            for(i=0; i<=n-1; i++)
+            {
+                tags[i] = i;
+            }
+            nearestneighbor.kdtreebuildtagged(xy, tags, n, nx, 1, 2, z.tree);
+            
+            //
+            // build nodal functions
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                for(i_=0; i_<=nx;i_++)
+                {
+                    z.q[i,i_] = xy[i,i_];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        IDW model for noisy data.
+
+        This subroutine may be used to handle noisy data, i.e. data with noise  in
+        OUTPUT values.  It differs from IDWBuildModifiedShepard() in the following
+        aspects:
+        * nodal functions are not constrained to pass through  nodes:  Qi(xi)<>yi,
+          i.e. we have fitting  instead  of  interpolation.
+        * weights which are used during least  squares fitting stage are all equal
+          to 1.0 (independently of distance)
+        * "fast"-linear or constant nodal functions are not supported (either  not
+          robust enough or too rigid)
+
+        This problem require far more complex tuning than interpolation  problems.
+        Below you can find some recommendations regarding this problem:
+        * focus on tuning NQ; it controls noise reduction. As for NW, you can just
+          make it equal to 2*NQ.
+        * you can use cross-validation to determine optimal NQ.
+        * optimal NQ is a result of complex tradeoff  between  noise  level  (more
+          noise = larger NQ required) and underlying  function  complexity  (given
+          fixed N, larger NQ means smoothing of compex features in the data).  For
+          example, NQ=N will reduce noise to the minimum level possible,  but  you
+          will end up with just constant/linear/quadratic (depending on  D)  least
+          squares model for the whole dataset.
+
+        INPUT PARAMETERS:
+            XY  -   X and Y values, array[0..N-1,0..NX].
+                    First NX columns contain X-values, last column contain
+                    Y-values.
+            N   -   number of nodes, N>0.
+            NX  -   space dimension, NX>=1.
+            D   -   nodal function degree, either:
+                    * 1     linear model, least squares fitting. Simpe  model  for
+                            datasets too small for quadratic models (or  for  very
+                            noisy problems).
+                    * 2     quadratic  model,  least  squares  fitting. Best model
+                            available (if your dataset is large enough).
+            NQ  -   number of points used to calculate nodal functions.  NQ should
+                    be  significantly   larger   than  1.5  times  the  number  of
+                    coefficients in a nodal function to overcome effects of noise:
+                    * larger than 1.5*(1+NX) for linear model,
+                    * larger than 3/4*(NX+2)*(NX+1) for quadratic model.
+                    Values less than this threshold will be silently increased.
+            NW  -   number of points used to calculate weights and to interpolate.
+                    Required: >=2^NX+1, values less than this  threshold  will  be
+                    silently increased.
+                    Recommended value: about 2*NQ or larger
+
+        OUTPUT PARAMETERS:
+            Z   -   IDW interpolant.
+
+        NOTES:
+          * best results are obtained with quadratic models, linear models are not
+            recommended to use unless you are pretty sure that it is what you want
+          * this subroutine is always succeeds (as long as correct parameters  are
+            passed).
+          * see  'Multivariate  Interpolation  of Large Sets of Scattered Data' by
+            Robert J. Renka for more information on this algorithm.
+
+
+          -- ALGLIB PROJECT --
+             Copyright 02.03.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void idwbuildnoisy(double[,] xy,
+            int n,
+            int nx,
+            int d,
+            int nq,
+            int nw,
+            idwinterpolant z)
+        {
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int j2 = 0;
+            int j3 = 0;
+            double v = 0;
+            int nc = 0;
+            int offs = 0;
+            double taskrcond = 0;
+            double[] x = new double[0];
+            double[] qrbuf = new double[0];
+            double[,] qxybuf = new double[0,0];
+            double[] y = new double[0];
+            double[] w = new double[0];
+            double[,] fmatrix = new double[0,0];
+            double[] qsol = new double[0];
+            int[] tags = new int[0];
+            double[] temp = new double[0];
+            int info = 0;
+            int i_ = 0;
+
+            
+            //
+            // these initializers are not really necessary,
+            // but without them compiler complains about uninitialized locals
+            //
+            nc = 0;
+            
+            //
+            // assertions
+            //
+            alglib.ap.assert(n>0, "IDWBuildNoisy: N<=0!");
+            alglib.ap.assert(nx>=1, "IDWBuildNoisy: NX<1!");
+            alglib.ap.assert(d>=1 && d<=2, "IDWBuildNoisy: D<>1 and D<>2!");
+            
+            //
+            // Correct parameters if needed
+            //
+            if( d==1 )
+            {
+                nq = Math.Max(nq, (int)Math.Ceiling(idwqfactor*(1+nx))+1);
+            }
+            if( d==2 )
+            {
+                nq = Math.Max(nq, (int)Math.Ceiling(idwqfactor*(nx+2)*(nx+1)/2)+1);
+            }
+            nw = Math.Max(nw, (int)Math.Round(Math.Pow(2, nx))+1);
+            nq = Math.Min(nq, n);
+            nw = Math.Min(nw, n);
+            
+            //
+            // primary initialization of Z
+            //
+            idwinit1(n, nx, d, nq, nw, z);
+            z.modeltype = 0;
+            
+            //
+            // Create KD-tree
+            //
+            tags = new int[n];
+            for(i=0; i<=n-1; i++)
+            {
+                tags[i] = i;
+            }
+            nearestneighbor.kdtreebuildtagged(xy, tags, n, nx, 1, 2, z.tree);
+            
+            //
+            // build nodal functions
+            // (special algorithm for noisy data is used)
+            //
+            temp = new double[nq+1];
+            x = new double[nx];
+            qrbuf = new double[nq];
+            qxybuf = new double[nq, nx+1];
+            if( d==1 )
+            {
+                y = new double[nq];
+                w = new double[nq];
+                qsol = new double[1+nx];
+                
+                //
+                // 1 for constant member,
+                // NX for linear members,
+                // 1 for temporary storage
+                //
+                fmatrix = new double[nq, 1+nx+1];
+            }
+            if( d==2 )
+            {
+                y = new double[nq];
+                w = new double[nq];
+                qsol = new double[1+nx+(int)Math.Round(nx*(nx+1)*0.5)];
+                
+                //
+                // 1 for constant member,
+                // NX for linear members,
+                // Round(NX*(NX+1)*0.5) for quadratic model,
+                // 1 for temporary storage
+                //
+                fmatrix = new double[nq, 1+nx+(int)Math.Round(nx*(nx+1)*0.5)+1];
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                
+                //
+                // Initialize center.
+                //
+                for(i_=0; i_<=nx-1;i_++)
+                {
+                    z.q[i,i_] = xy[i,i_];
+                }
+                
+                //
+                // Calculate linear/quadratic members
+                // using least squares fit
+                // NOTE 1: all weight are equal to 1.0
+                // NOTE 2: self-match is USED for this query
+                //
+                for(i_=0; i_<=nx-1;i_++)
+                {
+                    x[i_] = xy[i,i_];
+                }
+                k = nearestneighbor.kdtreequeryknn(z.tree, x, nq, true);
+                nearestneighbor.kdtreequeryresultsxy(z.tree, ref qxybuf);
+                nearestneighbor.kdtreequeryresultsdistances(z.tree, ref qrbuf);
+                if( d==1 )
+                {
+                    
+                    //
+                    // Linear nodal function calculated using
+                    // least squares fitting to its neighbors
+                    //
+                    for(j=0; j<=k-1; j++)
+                    {
+                        fmatrix[j,0] = 1.0;
+                        for(j2=0; j2<=nx-1; j2++)
+                        {
+                            fmatrix[j,1+j2] = qxybuf[j,j2]-xy[i,j2];
+                        }
+                        y[j] = qxybuf[j,nx];
+                        w[j] = 1;
+                    }
+                    nc = 1+nx;
+                }
+                if( d==2 )
+                {
+                    
+                    //
+                    // Quadratic nodal function calculated using
+                    // least squares fitting to its neighbors
+                    //
+                    for(j=0; j<=k-1; j++)
+                    {
+                        fmatrix[j,0] = 1;
+                        offs = 1;
+                        for(j2=0; j2<=nx-1; j2++)
+                        {
+                            fmatrix[j,offs] = qxybuf[j,j2]-xy[i,j2];
+                            offs = offs+1;
+                        }
+                        for(j2=0; j2<=nx-1; j2++)
+                        {
+                            for(j3=j2; j3<=nx-1; j3++)
+                            {
+                                fmatrix[j,offs] = (qxybuf[j,j2]-xy[i,j2])*(qxybuf[j,j3]-xy[i,j3]);
+                                offs = offs+1;
+                            }
+                        }
+                        y[j] = qxybuf[j,nx];
+                        w[j] = 1;
+                    }
+                    nc = 1+nx+(int)Math.Round(nx*(nx+1)*0.5);
+                }
+                idwinternalsolver(ref y, ref w, ref fmatrix, ref temp, k, nc, ref info, ref qsol, ref taskrcond);
+                
+                //
+                // Least squares models: copy results
+                //
+                if( info>0 )
+                {
+                    
+                    //
+                    // LLS task is solved, copy results
+                    //
+                    z.debugworstrcond = Math.Min(z.debugworstrcond, taskrcond);
+                    z.debugbestrcond = Math.Max(z.debugbestrcond, taskrcond);
+                    for(j=0; j<=nc-1; j++)
+                    {
+                        z.q[i,nx+j] = qsol[j];
+                    }
+                }
+                else
+                {
+                    
+                    //
+                    // Solver failure, very strange, but we will use
+                    // zero values to handle it.
+                    //
+                    z.debugsolverfailures = z.debugsolverfailures+1;
+                    v = 0;
+                    for(j=0; j<=k-1; j++)
+                    {
+                        v = v+qxybuf[j,nx];
+                    }
+                    z.q[i,nx] = v/k;
+                    for(j=0; j<=nc-2; j++)
+                    {
+                        z.q[i,nx+1+j] = 0;
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Internal subroutine: K-th nodal function calculation
+
+          -- ALGLIB --
+             Copyright 02.03.2010 by Bochkanov Sergey
+        *************************************************************************/
+        private static double idwcalcq(idwinterpolant z,
+            double[] x,
+            int k)
+        {
+            double result = 0;
+            int nx = 0;
+            int i = 0;
+            int j = 0;
+            int offs = 0;
+
+            nx = z.nx;
+            
+            //
+            // constant member
+            //
+            result = z.q[k,nx];
+            
+            //
+            // linear members
+            //
+            if( z.d>=1 )
+            {
+                for(i=0; i<=nx-1; i++)
+                {
+                    result = result+z.q[k,nx+1+i]*(x[i]-z.q[k,i]);
+                }
+            }
+            
+            //
+            // quadratic members
+            //
+            if( z.d>=2 )
+            {
+                offs = nx+1+nx;
+                for(i=0; i<=nx-1; i++)
+                {
+                    for(j=i; j<=nx-1; j++)
+                    {
+                        result = result+z.q[k,offs]*(x[i]-z.q[k,i])*(x[j]-z.q[k,j]);
+                        offs = offs+1;
+                    }
+                }
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        Initialization of internal structures.
+
+        It assumes correctness of all parameters.
+
+          -- ALGLIB --
+             Copyright 02.03.2010 by Bochkanov Sergey
+        *************************************************************************/
+        private static void idwinit1(int n,
+            int nx,
+            int d,
+            int nq,
+            int nw,
+            idwinterpolant z)
+        {
+            z.debugsolverfailures = 0;
+            z.debugworstrcond = 1.0;
+            z.debugbestrcond = 0;
+            z.n = n;
+            z.nx = nx;
+            z.d = 0;
+            if( d==1 )
+            {
+                z.d = 1;
+            }
+            if( d==2 )
+            {
+                z.d = 2;
+            }
+            if( d==-1 )
+            {
+                z.d = 1;
+            }
+            z.nw = nw;
+            if( d==-1 )
+            {
+                z.q = new double[n, nx+1+nx];
+            }
+            if( d==0 )
+            {
+                z.q = new double[n, nx+1];
+            }
+            if( d==1 )
+            {
+                z.q = new double[n, nx+1+nx];
+            }
+            if( d==2 )
+            {
+                z.q = new double[n, nx+1+nx+(int)Math.Round(nx*(nx+1)*0.5)];
+            }
+            z.tbuf = new int[nw];
+            z.rbuf = new double[nw];
+            z.xybuf = new double[nw, nx+1];
+            z.xbuf = new double[nx];
+        }
+
+
+        /*************************************************************************
+        Linear least squares solver for small tasks.
+
+        Works faster than standard ALGLIB solver in non-degenerate cases  (due  to
+        absense of internal allocations and optimized row/colums).  In  degenerate
+        cases it calls standard solver, which results in small performance penalty
+        associated with preliminary steps.
+
+        INPUT PARAMETERS:
+            Y           array[0..N-1]
+            W           array[0..N-1]
+            FMatrix     array[0..N-1,0..M], have additional column for temporary
+                        values
+            Temp        array[0..N]
+        *************************************************************************/
+        private static void idwinternalsolver(ref double[] y,
+            ref double[] w,
+            ref double[,] fmatrix,
+            ref double[] temp,
+            int n,
+            int m,
+            ref int info,
+            ref double[] x,
+            ref double taskrcond)
+        {
+            int i = 0;
+            int j = 0;
+            double v = 0;
+            double tau = 0;
+            double[] b = new double[0];
+            densesolver.densesolverlsreport srep = new densesolver.densesolverlsreport();
+            int i_ = 0;
+            int i1_ = 0;
+
+            info = 0;
+
+            
+            //
+            // set up info
+            //
+            info = 1;
+            
+            //
+            // prepare matrix
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                fmatrix[i,m] = y[i];
+                v = w[i];
+                for(i_=0; i_<=m;i_++)
+                {
+                    fmatrix[i,i_] = v*fmatrix[i,i_];
+                }
+            }
+            
+            //
+            // use either fast algorithm or general algorithm
+            //
+            if( m<=n )
+            {
+                
+                //
+                // QR decomposition
+                // We assume that M<=N (we would have called LSFit() otherwise)
+                //
+                for(i=0; i<=m-1; i++)
+                {
+                    if( i<n-1 )
+                    {
+                        i1_ = (i) - (1);
+                        for(i_=1; i_<=n-i;i_++)
+                        {
+                            temp[i_] = fmatrix[i_+i1_,i];
+                        }
+                        reflections.generatereflection(ref temp, n-i, ref tau);
+                        fmatrix[i,i] = temp[1];
+                        temp[1] = 1;
+                        for(j=i+1; j<=m; j++)
+                        {
+                            i1_ = (1)-(i);
+                            v = 0.0;
+                            for(i_=i; i_<=n-1;i_++)
+                            {
+                                v += fmatrix[i_,j]*temp[i_+i1_];
+                            }
+                            v = tau*v;
+                            i1_ = (1) - (i);
+                            for(i_=i; i_<=n-1;i_++)
+                            {
+                                fmatrix[i_,j] = fmatrix[i_,j] - v*temp[i_+i1_];
+                            }
+                        }
+                    }
+                }
+                
+                //
+                // Check condition number
+                //
+                taskrcond = rcond.rmatrixtrrcondinf(fmatrix, m, true, false);
+                
+                //
+                // use either fast algorithm for non-degenerate cases
+                // or slow algorithm for degenerate cases
+                //
+                if( (double)(taskrcond)>(double)(10000*n*math.machineepsilon) )
+                {
+                    
+                    //
+                    // solve triangular system R*x = FMatrix[0:M-1,M]
+                    // using fast algorithm, then exit
+                    //
+                    x[m-1] = fmatrix[m-1,m]/fmatrix[m-1,m-1];
+                    for(i=m-2; i>=0; i--)
+                    {
+                        v = 0.0;
+                        for(i_=i+1; i_<=m-1;i_++)
+                        {
+                            v += fmatrix[i,i_]*x[i_];
+                        }
+                        x[i] = (fmatrix[i,m]-v)/fmatrix[i,i];
+                    }
+                }
+                else
+                {
+                    
+                    //
+                    // use more general algorithm
+                    //
+                    b = new double[m];
+                    for(i=0; i<=m-1; i++)
+                    {
+                        for(j=0; j<=i-1; j++)
+                        {
+                            fmatrix[i,j] = 0.0;
+                        }
+                        b[i] = fmatrix[i,m];
+                    }
+                    densesolver.rmatrixsolvels(fmatrix, m, m, b, 10000*math.machineepsilon, ref info, srep, ref x);
+                }
+            }
+            else
+            {
+                
+                //
+                // use more general algorithm
+                //
+                b = new double[n];
+                for(i=0; i<=n-1; i++)
+                {
+                    b[i] = fmatrix[i,m];
+                }
+                densesolver.rmatrixsolvels(fmatrix, n, m, b, 10000*math.machineepsilon, ref info, srep, ref x);
+                taskrcond = srep.r2;
+            }
+        }
+
+
+    }
+    public class ratint
+    {
+        /*************************************************************************
+        Barycentric interpolant.
+        *************************************************************************/
+        public class barycentricinterpolant
+        {
+            public int n;
+            public double sy;
+            public double[] x;
+            public double[] y;
+            public double[] w;
+            public barycentricinterpolant()
+            {
+                x = new double[0];
+                y = new double[0];
+                w = new double[0];
+            }
+        };
+
+
+
+
+        /*************************************************************************
+        Rational interpolation using barycentric formula
+
+        F(t) = SUM(i=0,n-1,w[i]*f[i]/(t-x[i])) / SUM(i=0,n-1,w[i]/(t-x[i]))
+
+        Input parameters:
+            B   -   barycentric interpolant built with one of model building
+                    subroutines.
+            T   -   interpolation point
+
+        Result:
+            barycentric interpolant F(t)
+
+          -- ALGLIB --
+             Copyright 17.08.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static double barycentriccalc(barycentricinterpolant b,
+            double t)
+        {
+            double result = 0;
+            double s1 = 0;
+            double s2 = 0;
+            double s = 0;
+            double v = 0;
+            int i = 0;
+
+            alglib.ap.assert(!Double.IsInfinity(t), "BarycentricCalc: infinite T!");
+            
+            //
+            // special case: NaN
+            //
+            if( Double.IsNaN(t) )
+            {
+                result = Double.NaN;
+                return result;
+            }
+            
+            //
+            // special case: N=1
+            //
+            if( b.n==1 )
+            {
+                result = b.sy*b.y[0];
+                return result;
+            }
+            
+            //
+            // Here we assume that task is normalized, i.e.:
+            // 1. abs(Y[i])<=1
+            // 2. abs(W[i])<=1
+            // 3. X[] is ordered
+            //
+            s = Math.Abs(t-b.x[0]);
+            for(i=0; i<=b.n-1; i++)
+            {
+                v = b.x[i];
+                if( (double)(v)==(double)(t) )
+                {
+                    result = b.sy*b.y[i];
+                    return result;
+                }
+                v = Math.Abs(t-v);
+                if( (double)(v)<(double)(s) )
+                {
+                    s = v;
+                }
+            }
+            s1 = 0;
+            s2 = 0;
+            for(i=0; i<=b.n-1; i++)
+            {
+                v = s/(t-b.x[i]);
+                v = v*b.w[i];
+                s1 = s1+v*b.y[i];
+                s2 = s2+v;
+            }
+            result = b.sy*s1/s2;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Differentiation of barycentric interpolant: first derivative.
+
+        Algorithm used in this subroutine is very robust and should not fail until
+        provided with values too close to MaxRealNumber  (usually  MaxRealNumber/N
+        or greater will overflow).
+
+        INPUT PARAMETERS:
+            B   -   barycentric interpolant built with one of model building
+                    subroutines.
+            T   -   interpolation point
+
+        OUTPUT PARAMETERS:
+            F   -   barycentric interpolant at T
+            DF  -   first derivative
+            
+        NOTE
+
+
+          -- ALGLIB --
+             Copyright 17.08.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void barycentricdiff1(barycentricinterpolant b,
+            double t,
+            ref double f,
+            ref double df)
+        {
+            double v = 0;
+            double vv = 0;
+            int i = 0;
+            int k = 0;
+            double n0 = 0;
+            double n1 = 0;
+            double d0 = 0;
+            double d1 = 0;
+            double s0 = 0;
+            double s1 = 0;
+            double xk = 0;
+            double xi = 0;
+            double xmin = 0;
+            double xmax = 0;
+            double xscale1 = 0;
+            double xoffs1 = 0;
+            double xscale2 = 0;
+            double xoffs2 = 0;
+            double xprev = 0;
+
+            f = 0;
+            df = 0;
+
+            alglib.ap.assert(!Double.IsInfinity(t), "BarycentricDiff1: infinite T!");
+            
+            //
+            // special case: NaN
+            //
+            if( Double.IsNaN(t) )
+            {
+                f = Double.NaN;
+                df = Double.NaN;
+                return;
+            }
+            
+            //
+            // special case: N=1
+            //
+            if( b.n==1 )
+            {
+                f = b.sy*b.y[0];
+                df = 0;
+                return;
+            }
+            if( (double)(b.sy)==(double)(0) )
+            {
+                f = 0;
+                df = 0;
+                return;
+            }
+            alglib.ap.assert((double)(b.sy)>(double)(0), "BarycentricDiff1: internal error");
+            
+            //
+            // We assume than N>1 and B.SY>0. Find:
+            // 1. pivot point (X[i] closest to T)
+            // 2. width of interval containing X[i]
+            //
+            v = Math.Abs(b.x[0]-t);
+            k = 0;
+            xmin = b.x[0];
+            xmax = b.x[0];
+            for(i=1; i<=b.n-1; i++)
+            {
+                vv = b.x[i];
+                if( (double)(Math.Abs(vv-t))<(double)(v) )
+                {
+                    v = Math.Abs(vv-t);
+                    k = i;
+                }
+                xmin = Math.Min(xmin, vv);
+                xmax = Math.Max(xmax, vv);
+            }
+            
+            //
+            // pivot point found, calculate dNumerator and dDenominator
+            //
+            xscale1 = 1/(xmax-xmin);
+            xoffs1 = -(xmin/(xmax-xmin))+1;
+            xscale2 = 2;
+            xoffs2 = -3;
+            t = t*xscale1+xoffs1;
+            t = t*xscale2+xoffs2;
+            xk = b.x[k];
+            xk = xk*xscale1+xoffs1;
+            xk = xk*xscale2+xoffs2;
+            v = t-xk;
+            n0 = 0;
+            n1 = 0;
+            d0 = 0;
+            d1 = 0;
+            xprev = -2;
+            for(i=0; i<=b.n-1; i++)
+            {
+                xi = b.x[i];
+                xi = xi*xscale1+xoffs1;
+                xi = xi*xscale2+xoffs2;
+                alglib.ap.assert((double)(xi)>(double)(xprev), "BarycentricDiff1: points are too close!");
+                xprev = xi;
+                if( i!=k )
+                {
+                    vv = math.sqr(t-xi);
+                    s0 = (t-xk)/(t-xi);
+                    s1 = (xk-xi)/vv;
+                }
+                else
+                {
+                    s0 = 1;
+                    s1 = 0;
+                }
+                vv = b.w[i]*b.y[i];
+                n0 = n0+s0*vv;
+                n1 = n1+s1*vv;
+                vv = b.w[i];
+                d0 = d0+s0*vv;
+                d1 = d1+s1*vv;
+            }
+            f = b.sy*n0/d0;
+            df = (n1*d0-n0*d1)/math.sqr(d0);
+            if( (double)(df)!=(double)(0) )
+            {
+                df = Math.Sign(df)*Math.Exp(Math.Log(Math.Abs(df))+Math.Log(b.sy)+Math.Log(xscale1)+Math.Log(xscale2));
+            }
+        }
+
+
+        /*************************************************************************
+        Differentiation of barycentric interpolant: first/second derivatives.
+
+        INPUT PARAMETERS:
+            B   -   barycentric interpolant built with one of model building
+                    subroutines.
+            T   -   interpolation point
+
+        OUTPUT PARAMETERS:
+            F   -   barycentric interpolant at T
+            DF  -   first derivative
+            D2F -   second derivative
+
+        NOTE: this algorithm may fail due to overflow/underflor if  used  on  data
+        whose values are close to MaxRealNumber or MinRealNumber.  Use more robust
+        BarycentricDiff1() subroutine in such cases.
+
+
+          -- ALGLIB --
+             Copyright 17.08.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void barycentricdiff2(barycentricinterpolant b,
+            double t,
+            ref double f,
+            ref double df,
+            ref double d2f)
+        {
+            double v = 0;
+            double vv = 0;
+            int i = 0;
+            int k = 0;
+            double n0 = 0;
+            double n1 = 0;
+            double n2 = 0;
+            double d0 = 0;
+            double d1 = 0;
+            double d2 = 0;
+            double s0 = 0;
+            double s1 = 0;
+            double s2 = 0;
+            double xk = 0;
+            double xi = 0;
+
+            f = 0;
+            df = 0;
+            d2f = 0;
+
+            alglib.ap.assert(!Double.IsInfinity(t), "BarycentricDiff1: infinite T!");
+            
+            //
+            // special case: NaN
+            //
+            if( Double.IsNaN(t) )
+            {
+                f = Double.NaN;
+                df = Double.NaN;
+                d2f = Double.NaN;
+                return;
+            }
+            
+            //
+            // special case: N=1
+            //
+            if( b.n==1 )
+            {
+                f = b.sy*b.y[0];
+                df = 0;
+                d2f = 0;
+                return;
+            }
+            if( (double)(b.sy)==(double)(0) )
+            {
+                f = 0;
+                df = 0;
+                d2f = 0;
+                return;
+            }
+            
+            //
+            // We assume than N>1 and B.SY>0. Find:
+            // 1. pivot point (X[i] closest to T)
+            // 2. width of interval containing X[i]
+            //
+            alglib.ap.assert((double)(b.sy)>(double)(0), "BarycentricDiff: internal error");
+            f = 0;
+            df = 0;
+            d2f = 0;
+            v = Math.Abs(b.x[0]-t);
+            k = 0;
+            for(i=1; i<=b.n-1; i++)
+            {
+                vv = b.x[i];
+                if( (double)(Math.Abs(vv-t))<(double)(v) )
+                {
+                    v = Math.Abs(vv-t);
+                    k = i;
+                }
+            }
+            
+            //
+            // pivot point found, calculate dNumerator and dDenominator
+            //
+            xk = b.x[k];
+            v = t-xk;
+            n0 = 0;
+            n1 = 0;
+            n2 = 0;
+            d0 = 0;
+            d1 = 0;
+            d2 = 0;
+            for(i=0; i<=b.n-1; i++)
+            {
+                if( i!=k )
+                {
+                    xi = b.x[i];
+                    vv = math.sqr(t-xi);
+                    s0 = (t-xk)/(t-xi);
+                    s1 = (xk-xi)/vv;
+                    s2 = -(2*(xk-xi)/(vv*(t-xi)));
+                }
+                else
+                {
+                    s0 = 1;
+                    s1 = 0;
+                    s2 = 0;
+                }
+                vv = b.w[i]*b.y[i];
+                n0 = n0+s0*vv;
+                n1 = n1+s1*vv;
+                n2 = n2+s2*vv;
+                vv = b.w[i];
+                d0 = d0+s0*vv;
+                d1 = d1+s1*vv;
+                d2 = d2+s2*vv;
+            }
+            f = b.sy*n0/d0;
+            df = b.sy*(n1*d0-n0*d1)/math.sqr(d0);
+            d2f = b.sy*((n2*d0-n0*d2)*math.sqr(d0)-(n1*d0-n0*d1)*2*d0*d1)/math.sqr(math.sqr(d0));
+        }
+
+
+        /*************************************************************************
+        This subroutine performs linear transformation of the argument.
+
+        INPUT PARAMETERS:
+            B       -   rational interpolant in barycentric form
+            CA, CB  -   transformation coefficients: x = CA*t + CB
+
+        OUTPUT PARAMETERS:
+            B       -   transformed interpolant with X replaced by T
+
+          -- ALGLIB PROJECT --
+             Copyright 19.08.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void barycentriclintransx(barycentricinterpolant b,
+            double ca,
+            double cb)
+        {
+            int i = 0;
+            int j = 0;
+            double v = 0;
+
+            
+            //
+            // special case, replace by constant F(CB)
+            //
+            if( (double)(ca)==(double)(0) )
+            {
+                b.sy = barycentriccalc(b, cb);
+                v = 1;
+                for(i=0; i<=b.n-1; i++)
+                {
+                    b.y[i] = 1;
+                    b.w[i] = v;
+                    v = -v;
+                }
+                return;
+            }
+            
+            //
+            // general case: CA<>0
+            //
+            for(i=0; i<=b.n-1; i++)
+            {
+                b.x[i] = (b.x[i]-cb)/ca;
+            }
+            if( (double)(ca)<(double)(0) )
+            {
+                for(i=0; i<=b.n-1; i++)
+                {
+                    if( i<b.n-1-i )
+                    {
+                        j = b.n-1-i;
+                        v = b.x[i];
+                        b.x[i] = b.x[j];
+                        b.x[j] = v;
+                        v = b.y[i];
+                        b.y[i] = b.y[j];
+                        b.y[j] = v;
+                        v = b.w[i];
+                        b.w[i] = b.w[j];
+                        b.w[j] = v;
+                    }
+                    else
+                    {
+                        break;
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This  subroutine   performs   linear  transformation  of  the  barycentric
+        interpolant.
+
+        INPUT PARAMETERS:
+            B       -   rational interpolant in barycentric form
+            CA, CB  -   transformation coefficients: B2(x) = CA*B(x) + CB
+
+        OUTPUT PARAMETERS:
+            B       -   transformed interpolant
+
+          -- ALGLIB PROJECT --
+             Copyright 19.08.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void barycentriclintransy(barycentricinterpolant b,
+            double ca,
+            double cb)
+        {
+            int i = 0;
+            double v = 0;
+            int i_ = 0;
+
+            for(i=0; i<=b.n-1; i++)
+            {
+                b.y[i] = ca*b.sy*b.y[i]+cb;
+            }
+            b.sy = 0;
+            for(i=0; i<=b.n-1; i++)
+            {
+                b.sy = Math.Max(b.sy, Math.Abs(b.y[i]));
+            }
+            if( (double)(b.sy)>(double)(0) )
+            {
+                v = 1/b.sy;
+                for(i_=0; i_<=b.n-1;i_++)
+                {
+                    b.y[i_] = v*b.y[i_];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Extracts X/Y/W arrays from rational interpolant
+
+        INPUT PARAMETERS:
+            B   -   barycentric interpolant
+
+        OUTPUT PARAMETERS:
+            N   -   nodes count, N>0
+            X   -   interpolation nodes, array[0..N-1]
+            F   -   function values, array[0..N-1]
+            W   -   barycentric weights, array[0..N-1]
+
+          -- ALGLIB --
+             Copyright 17.08.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void barycentricunpack(barycentricinterpolant b,
+            ref int n,
+            ref double[] x,
+            ref double[] y,
+            ref double[] w)
+        {
+            double v = 0;
+            int i_ = 0;
+
+            n = 0;
+            x = new double[0];
+            y = new double[0];
+            w = new double[0];
+
+            n = b.n;
+            x = new double[n];
+            y = new double[n];
+            w = new double[n];
+            v = b.sy;
+            for(i_=0; i_<=n-1;i_++)
+            {
+                x[i_] = b.x[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                y[i_] = v*b.y[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                w[i_] = b.w[i_];
+            }
+        }
+
+
+        /*************************************************************************
+        Rational interpolant from X/Y/W arrays
+
+        F(t) = SUM(i=0,n-1,w[i]*f[i]/(t-x[i])) / SUM(i=0,n-1,w[i]/(t-x[i]))
+
+        INPUT PARAMETERS:
+            X   -   interpolation nodes, array[0..N-1]
+            F   -   function values, array[0..N-1]
+            W   -   barycentric weights, array[0..N-1]
+            N   -   nodes count, N>0
+
+        OUTPUT PARAMETERS:
+            B   -   barycentric interpolant built from (X, Y, W)
+
+          -- ALGLIB --
+             Copyright 17.08.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void barycentricbuildxyw(double[] x,
+            double[] y,
+            double[] w,
+            int n,
+            barycentricinterpolant b)
+        {
+            int i_ = 0;
+
+            alglib.ap.assert(n>0, "BarycentricBuildXYW: incorrect N!");
+            
+            //
+            // fill X/Y/W
+            //
+            b.x = new double[n];
+            b.y = new double[n];
+            b.w = new double[n];
+            for(i_=0; i_<=n-1;i_++)
+            {
+                b.x[i_] = x[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                b.y[i_] = y[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                b.w[i_] = w[i_];
+            }
+            b.n = n;
+            
+            //
+            // Normalize
+            //
+            barycentricnormalize(b);
+        }
+
+
+        /*************************************************************************
+        Rational interpolant without poles
+
+        The subroutine constructs the rational interpolating function without real
+        poles  (see  'Barycentric rational interpolation with no  poles  and  high
+        rates of approximation', Michael S. Floater. and  Kai  Hormann,  for  more
+        information on this subject).
+
+        Input parameters:
+            X   -   interpolation nodes, array[0..N-1].
+            Y   -   function values, array[0..N-1].
+            N   -   number of nodes, N>0.
+            D   -   order of the interpolation scheme, 0 <= D <= N-1.
+                    D<0 will cause an error.
+                    D>=N it will be replaced with D=N-1.
+                    if you don't know what D to choose, use small value about 3-5.
+
+        Output parameters:
+            B   -   barycentric interpolant.
+
+        Note:
+            this algorithm always succeeds and calculates the weights  with  close
+            to machine precision.
+
+          -- ALGLIB PROJECT --
+             Copyright 17.06.2007 by Bochkanov Sergey
+        *************************************************************************/
+        public static void barycentricbuildfloaterhormann(double[] x,
+            double[] y,
+            int n,
+            int d,
+            barycentricinterpolant b)
+        {
+            double s0 = 0;
+            double s = 0;
+            double v = 0;
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int[] perm = new int[0];
+            double[] wtemp = new double[0];
+            double[] sortrbuf = new double[0];
+            double[] sortrbuf2 = new double[0];
+            int i_ = 0;
+
+            alglib.ap.assert(n>0, "BarycentricFloaterHormann: N<=0!");
+            alglib.ap.assert(d>=0, "BarycentricFloaterHormann: incorrect D!");
+            
+            //
+            // Prepare
+            //
+            if( d>n-1 )
+            {
+                d = n-1;
+            }
+            b.n = n;
+            
+            //
+            // special case: N=1
+            //
+            if( n==1 )
+            {
+                b.x = new double[n];
+                b.y = new double[n];
+                b.w = new double[n];
+                b.x[0] = x[0];
+                b.y[0] = y[0];
+                b.w[0] = 1;
+                barycentricnormalize(b);
+                return;
+            }
+            
+            //
+            // Fill X/Y
+            //
+            b.x = new double[n];
+            b.y = new double[n];
+            for(i_=0; i_<=n-1;i_++)
+            {
+                b.x[i_] = x[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                b.y[i_] = y[i_];
+            }
+            tsort.tagsortfastr(ref b.x, ref b.y, ref sortrbuf, ref sortrbuf2, n);
+            
+            //
+            // Calculate Wk
+            //
+            b.w = new double[n];
+            s0 = 1;
+            for(k=1; k<=d; k++)
+            {
+                s0 = -s0;
+            }
+            for(k=0; k<=n-1; k++)
+            {
+                
+                //
+                // Wk
+                //
+                s = 0;
+                for(i=Math.Max(k-d, 0); i<=Math.Min(k, n-1-d); i++)
+                {
+                    v = 1;
+                    for(j=i; j<=i+d; j++)
+                    {
+                        if( j!=k )
+                        {
+                            v = v/Math.Abs(b.x[k]-b.x[j]);
+                        }
+                    }
+                    s = s+v;
+                }
+                b.w[k] = s0*s;
+                
+                //
+                // Next S0
+                //
+                s0 = -s0;
+            }
+            
+            //
+            // Normalize
+            //
+            barycentricnormalize(b);
+        }
+
+
+        /*************************************************************************
+        Copying of the barycentric interpolant (for internal use only)
+
+        INPUT PARAMETERS:
+            B   -   barycentric interpolant
+
+        OUTPUT PARAMETERS:
+            B2  -   copy(B1)
+
+          -- ALGLIB --
+             Copyright 17.08.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void barycentriccopy(barycentricinterpolant b,
+            barycentricinterpolant b2)
+        {
+            int i_ = 0;
+
+            b2.n = b.n;
+            b2.sy = b.sy;
+            b2.x = new double[b2.n];
+            b2.y = new double[b2.n];
+            b2.w = new double[b2.n];
+            for(i_=0; i_<=b2.n-1;i_++)
+            {
+                b2.x[i_] = b.x[i_];
+            }
+            for(i_=0; i_<=b2.n-1;i_++)
+            {
+                b2.y[i_] = b.y[i_];
+            }
+            for(i_=0; i_<=b2.n-1;i_++)
+            {
+                b2.w[i_] = b.w[i_];
+            }
+        }
+
+
+        /*************************************************************************
+        Normalization of barycentric interpolant:
+        * B.N, B.X, B.Y and B.W are initialized
+        * B.SY is NOT initialized
+        * Y[] is normalized, scaling coefficient is stored in B.SY
+        * W[] is normalized, no scaling coefficient is stored
+        * X[] is sorted
+
+        Internal subroutine.
+        *************************************************************************/
+        private static void barycentricnormalize(barycentricinterpolant b)
+        {
+            int[] p1 = new int[0];
+            int[] p2 = new int[0];
+            int i = 0;
+            int j = 0;
+            int j2 = 0;
+            double v = 0;
+            int i_ = 0;
+
+            
+            //
+            // Normalize task: |Y|<=1, |W|<=1, sort X[]
+            //
+            b.sy = 0;
+            for(i=0; i<=b.n-1; i++)
+            {
+                b.sy = Math.Max(b.sy, Math.Abs(b.y[i]));
+            }
+            if( (double)(b.sy)>(double)(0) && (double)(Math.Abs(b.sy-1))>(double)(10*math.machineepsilon) )
+            {
+                v = 1/b.sy;
+                for(i_=0; i_<=b.n-1;i_++)
+                {
+                    b.y[i_] = v*b.y[i_];
+                }
+            }
+            v = 0;
+            for(i=0; i<=b.n-1; i++)
+            {
+                v = Math.Max(v, Math.Abs(b.w[i]));
+            }
+            if( (double)(v)>(double)(0) && (double)(Math.Abs(v-1))>(double)(10*math.machineepsilon) )
+            {
+                v = 1/v;
+                for(i_=0; i_<=b.n-1;i_++)
+                {
+                    b.w[i_] = v*b.w[i_];
+                }
+            }
+            for(i=0; i<=b.n-2; i++)
+            {
+                if( (double)(b.x[i+1])<(double)(b.x[i]) )
+                {
+                    tsort.tagsort(ref b.x, b.n, ref p1, ref p2);
+                    for(j=0; j<=b.n-1; j++)
+                    {
+                        j2 = p2[j];
+                        v = b.y[j];
+                        b.y[j] = b.y[j2];
+                        b.y[j2] = v;
+                        v = b.w[j];
+                        b.w[j] = b.w[j2];
+                        b.w[j2] = v;
+                    }
+                    break;
+                }
+            }
+        }
+
+
+    }
+    public class polint
+    {
+        /*************************************************************************
+        Conversion from barycentric representation to Chebyshev basis.
+        This function has O(N^2) complexity.
+
+        INPUT PARAMETERS:
+            P   -   polynomial in barycentric form
+            A,B -   base interval for Chebyshev polynomials (see below)
+                    A<>B
+
+        OUTPUT PARAMETERS
+            T   -   coefficients of Chebyshev representation;
+                    P(x) = sum { T[i]*Ti(2*(x-A)/(B-A)-1), i=0..N-1 },
+                    where Ti - I-th Chebyshev polynomial.
+
+        NOTES:
+            barycentric interpolant passed as P may be either polynomial  obtained
+            from  polynomial  interpolation/ fitting or rational function which is
+            NOT polynomial. We can't distinguish between these two cases, and this
+            algorithm just tries to work assuming that P IS a polynomial.  If not,
+            algorithm will return results, but they won't have any meaning.
+
+          -- ALGLIB --
+             Copyright 30.09.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void polynomialbar2cheb(ratint.barycentricinterpolant p,
+            double a,
+            double b,
+            ref double[] t)
+        {
+            int i = 0;
+            int k = 0;
+            double[] vp = new double[0];
+            double[] vx = new double[0];
+            double[] tk = new double[0];
+            double[] tk1 = new double[0];
+            double v = 0;
+            int i_ = 0;
+
+            t = new double[0];
+
+            alglib.ap.assert(math.isfinite(a), "PolynomialBar2Cheb: A is not finite!");
+            alglib.ap.assert(math.isfinite(b), "PolynomialBar2Cheb: B is not finite!");
+            alglib.ap.assert((double)(a)!=(double)(b), "PolynomialBar2Cheb: A=B!");
+            alglib.ap.assert(p.n>0, "PolynomialBar2Cheb: P is not correctly initialized barycentric interpolant!");
+            
+            //
+            // Calculate function values on a Chebyshev grid
+            //
+            vp = new double[p.n];
+            vx = new double[p.n];
+            for(i=0; i<=p.n-1; i++)
+            {
+                vx[i] = Math.Cos(Math.PI*(i+0.5)/p.n);
+                vp[i] = ratint.barycentriccalc(p, 0.5*(vx[i]+1)*(b-a)+a);
+            }
+            
+            //
+            // T[0]
+            //
+            t = new double[p.n];
+            v = 0;
+            for(i=0; i<=p.n-1; i++)
+            {
+                v = v+vp[i];
+            }
+            t[0] = v/p.n;
+            
+            //
+            // other T's.
+            //
+            // NOTES:
+            // 1. TK stores T{k} on VX, TK1 stores T{k-1} on VX
+            // 2. we can do same calculations with fast DCT, but it
+            //    * adds dependencies
+            //    * still leaves us with O(N^2) algorithm because
+            //      preparation of function values is O(N^2) process
+            //
+            if( p.n>1 )
+            {
+                tk = new double[p.n];
+                tk1 = new double[p.n];
+                for(i=0; i<=p.n-1; i++)
+                {
+                    tk[i] = vx[i];
+                    tk1[i] = 1;
+                }
+                for(k=1; k<=p.n-1; k++)
+                {
+                    
+                    //
+                    // calculate discrete product of function vector and TK
+                    //
+                    v = 0.0;
+                    for(i_=0; i_<=p.n-1;i_++)
+                    {
+                        v += tk[i_]*vp[i_];
+                    }
+                    t[k] = v/(0.5*p.n);
+                    
+                    //
+                    // Update TK and TK1
+                    //
+                    for(i=0; i<=p.n-1; i++)
+                    {
+                        v = 2*vx[i]*tk[i]-tk1[i];
+                        tk1[i] = tk[i];
+                        tk[i] = v;
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Conversion from Chebyshev basis to barycentric representation.
+        This function has O(N^2) complexity.
+
+        INPUT PARAMETERS:
+            T   -   coefficients of Chebyshev representation;
+                    P(x) = sum { T[i]*Ti(2*(x-A)/(B-A)-1), i=0..N },
+                    where Ti - I-th Chebyshev polynomial.
+            N   -   number of coefficients:
+                    * if given, only leading N elements of T are used
+                    * if not given, automatically determined from size of T
+            A,B -   base interval for Chebyshev polynomials (see above)
+                    A<B
+
+        OUTPUT PARAMETERS
+            P   -   polynomial in barycentric form
+
+          -- ALGLIB --
+             Copyright 30.09.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void polynomialcheb2bar(double[] t,
+            int n,
+            double a,
+            double b,
+            ratint.barycentricinterpolant p)
+        {
+            int i = 0;
+            int k = 0;
+            double[] y = new double[0];
+            double tk = 0;
+            double tk1 = 0;
+            double vx = 0;
+            double vy = 0;
+            double v = 0;
+
+            alglib.ap.assert(math.isfinite(a), "PolynomialBar2Cheb: A is not finite!");
+            alglib.ap.assert(math.isfinite(b), "PolynomialBar2Cheb: B is not finite!");
+            alglib.ap.assert((double)(a)!=(double)(b), "PolynomialBar2Cheb: A=B!");
+            alglib.ap.assert(n>=1, "PolynomialBar2Cheb: N<1");
+            alglib.ap.assert(alglib.ap.len(t)>=n, "PolynomialBar2Cheb: Length(T)<N");
+            alglib.ap.assert(apserv.isfinitevector(t, n), "PolynomialBar2Cheb: T[] contains INF or NAN");
+            
+            //
+            // Calculate function values on a Chebyshev grid spanning [-1,+1]
+            //
+            y = new double[n];
+            for(i=0; i<=n-1; i++)
+            {
+                
+                //
+                // Calculate value on a grid spanning [-1,+1]
+                //
+                vx = Math.Cos(Math.PI*(i+0.5)/n);
+                vy = t[0];
+                tk1 = 1;
+                tk = vx;
+                for(k=1; k<=n-1; k++)
+                {
+                    vy = vy+t[k]*tk;
+                    v = 2*vx*tk-tk1;
+                    tk1 = tk;
+                    tk = v;
+                }
+                y[i] = vy;
+            }
+            
+            //
+            // Build barycentric interpolant, map grid from [-1,+1] to [A,B]
+            //
+            polynomialbuildcheb1(a, b, y, n, p);
+        }
+
+
+        /*************************************************************************
+        Conversion from barycentric representation to power basis.
+        This function has O(N^2) complexity.
+
+        INPUT PARAMETERS:
+            P   -   polynomial in barycentric form
+            C   -   offset (see below); 0.0 is used as default value.
+            S   -   scale (see below);  1.0 is used as default value. S<>0.
+
+        OUTPUT PARAMETERS
+            A   -   coefficients, P(x) = sum { A[i]*((X-C)/S)^i, i=0..N-1 }
+            N   -   number of coefficients (polynomial degree plus 1)
+
+        NOTES:
+        1.  this function accepts offset and scale, which can be  set  to  improve
+            numerical properties of polynomial. For example, if P was obtained  as
+            result of interpolation on [-1,+1],  you  can  set  C=0  and  S=1  and
+            represent  P  as sum of 1, x, x^2, x^3 and so on. In most cases you it
+            is exactly what you need.
+
+            However, if your interpolation model was built on [999,1001], you will
+            see significant growth of numerical errors when using {1, x, x^2, x^3}
+            as basis. Representing P as sum of 1, (x-1000), (x-1000)^2, (x-1000)^3
+            will be better option. Such representation can be  obtained  by  using
+            1000.0 as offset C and 1.0 as scale S.
+
+        2.  power basis is ill-conditioned and tricks described above can't  solve
+            this problem completely. This function  will  return  coefficients  in
+            any  case,  but  for  N>8  they  will  become unreliable. However, N's
+            less than 5 are pretty safe.
+            
+        3.  barycentric interpolant passed as P may be either polynomial  obtained
+            from  polynomial  interpolation/ fitting or rational function which is
+            NOT polynomial. We can't distinguish between these two cases, and this
+            algorithm just tries to work assuming that P IS a polynomial.  If not,
+            algorithm will return results, but they won't have any meaning.
+
+          -- ALGLIB --
+             Copyright 30.09.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void polynomialbar2pow(ratint.barycentricinterpolant p,
+            double c,
+            double s,
+            ref double[] a)
+        {
+            int i = 0;
+            int k = 0;
+            double e = 0;
+            double d = 0;
+            double[] vp = new double[0];
+            double[] vx = new double[0];
+            double[] tk = new double[0];
+            double[] tk1 = new double[0];
+            double[] t = new double[0];
+            double v = 0;
+            int i_ = 0;
+
+            a = new double[0];
+
+            alglib.ap.assert(math.isfinite(c), "PolynomialBar2Pow: C is not finite!");
+            alglib.ap.assert(math.isfinite(s), "PolynomialBar2Pow: S is not finite!");
+            alglib.ap.assert((double)(s)!=(double)(0), "PolynomialBar2Pow: S=0!");
+            alglib.ap.assert(p.n>0, "PolynomialBar2Pow: P is not correctly initialized barycentric interpolant!");
+            
+            //
+            // Calculate function values on a Chebyshev grid
+            //
+            vp = new double[p.n];
+            vx = new double[p.n];
+            for(i=0; i<=p.n-1; i++)
+            {
+                vx[i] = Math.Cos(Math.PI*(i+0.5)/p.n);
+                vp[i] = ratint.barycentriccalc(p, s*vx[i]+c);
+            }
+            
+            //
+            // T[0]
+            //
+            t = new double[p.n];
+            v = 0;
+            for(i=0; i<=p.n-1; i++)
+            {
+                v = v+vp[i];
+            }
+            t[0] = v/p.n;
+            
+            //
+            // other T's.
+            //
+            // NOTES:
+            // 1. TK stores T{k} on VX, TK1 stores T{k-1} on VX
+            // 2. we can do same calculations with fast DCT, but it
+            //    * adds dependencies
+            //    * still leaves us with O(N^2) algorithm because
+            //      preparation of function values is O(N^2) process
+            //
+            if( p.n>1 )
+            {
+                tk = new double[p.n];
+                tk1 = new double[p.n];
+                for(i=0; i<=p.n-1; i++)
+                {
+                    tk[i] = vx[i];
+                    tk1[i] = 1;
+                }
+                for(k=1; k<=p.n-1; k++)
+                {
+                    
+                    //
+                    // calculate discrete product of function vector and TK
+                    //
+                    v = 0.0;
+                    for(i_=0; i_<=p.n-1;i_++)
+                    {
+                        v += tk[i_]*vp[i_];
+                    }
+                    t[k] = v/(0.5*p.n);
+                    
+                    //
+                    // Update TK and TK1
+                    //
+                    for(i=0; i<=p.n-1; i++)
+                    {
+                        v = 2*vx[i]*tk[i]-tk1[i];
+                        tk1[i] = tk[i];
+                        tk[i] = v;
+                    }
+                }
+            }
+            
+            //
+            // Convert from Chebyshev basis to power basis
+            //
+            a = new double[p.n];
+            for(i=0; i<=p.n-1; i++)
+            {
+                a[i] = 0;
+            }
+            d = 0;
+            for(i=0; i<=p.n-1; i++)
+            {
+                for(k=i; k<=p.n-1; k++)
+                {
+                    e = a[k];
+                    a[k] = 0;
+                    if( i<=1 && k==i )
+                    {
+                        a[k] = 1;
+                    }
+                    else
+                    {
+                        if( i!=0 )
+                        {
+                            a[k] = 2*d;
+                        }
+                        if( k>i+1 )
+                        {
+                            a[k] = a[k]-a[k-2];
+                        }
+                    }
+                    d = e;
+                }
+                d = a[i];
+                e = 0;
+                k = i;
+                while( k<=p.n-1 )
+                {
+                    e = e+a[k]*t[k];
+                    k = k+2;
+                }
+                a[i] = e;
+            }
+        }
+
+
+        /*************************************************************************
+        Conversion from power basis to barycentric representation.
+        This function has O(N^2) complexity.
+
+        INPUT PARAMETERS:
+            A   -   coefficients, P(x) = sum { A[i]*((X-C)/S)^i, i=0..N-1 }
+            N   -   number of coefficients (polynomial degree plus 1)
+                    * if given, only leading N elements of A are used
+                    * if not given, automatically determined from size of A
+            C   -   offset (see below); 0.0 is used as default value.
+            S   -   scale (see below);  1.0 is used as default value. S<>0.
+
+        OUTPUT PARAMETERS
+            P   -   polynomial in barycentric form
+
+
+        NOTES:
+        1.  this function accepts offset and scale, which can be  set  to  improve
+            numerical properties of polynomial. For example, if you interpolate on
+            [-1,+1],  you  can  set C=0 and S=1 and convert from sum of 1, x, x^2,
+            x^3 and so on. In most cases you it is exactly what you need.
+
+            However, if your interpolation model was built on [999,1001], you will
+            see significant growth of numerical errors when using {1, x, x^2, x^3}
+            as  input  basis.  Converting  from  sum  of  1, (x-1000), (x-1000)^2,
+            (x-1000)^3 will be better option (you have to specify 1000.0 as offset
+            C and 1.0 as scale S).
+
+        2.  power basis is ill-conditioned and tricks described above can't  solve
+            this problem completely. This function  will  return barycentric model
+            in any case, but for N>8 accuracy well degrade. However, N's less than
+            5 are pretty safe.
+
+          -- ALGLIB --
+             Copyright 30.09.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void polynomialpow2bar(double[] a,
+            int n,
+            double c,
+            double s,
+            ratint.barycentricinterpolant p)
+        {
+            int i = 0;
+            int k = 0;
+            double[] y = new double[0];
+            double vx = 0;
+            double vy = 0;
+            double px = 0;
+
+            alglib.ap.assert(math.isfinite(c), "PolynomialPow2Bar: C is not finite!");
+            alglib.ap.assert(math.isfinite(s), "PolynomialPow2Bar: S is not finite!");
+            alglib.ap.assert((double)(s)!=(double)(0), "PolynomialPow2Bar: S is zero!");
+            alglib.ap.assert(n>=1, "PolynomialPow2Bar: N<1");
+            alglib.ap.assert(alglib.ap.len(a)>=n, "PolynomialPow2Bar: Length(A)<N");
+            alglib.ap.assert(apserv.isfinitevector(a, n), "PolynomialPow2Bar: A[] contains INF or NAN");
+            
+            //
+            // Calculate function values on a Chebyshev grid spanning [-1,+1]
+            //
+            y = new double[n];
+            for(i=0; i<=n-1; i++)
+            {
+                
+                //
+                // Calculate value on a grid spanning [-1,+1]
+                //
+                vx = Math.Cos(Math.PI*(i+0.5)/n);
+                vy = a[0];
+                px = vx;
+                for(k=1; k<=n-1; k++)
+                {
+                    vy = vy+px*a[k];
+                    px = px*vx;
+                }
+                y[i] = vy;
+            }
+            
+            //
+            // Build barycentric interpolant, map grid from [-1,+1] to [A,B]
+            //
+            polynomialbuildcheb1(c-s, c+s, y, n, p);
+        }
+
+
+        /*************************************************************************
+        Lagrange intepolant: generation of the model on the general grid.
+        This function has O(N^2) complexity.
+
+        INPUT PARAMETERS:
+            X   -   abscissas, array[0..N-1]
+            Y   -   function values, array[0..N-1]
+            N   -   number of points, N>=1
+
+        OUTPUT PARAMETERS
+            P   -   barycentric model which represents Lagrange interpolant
+                    (see ratint unit info and BarycentricCalc() description for
+                    more information).
+
+          -- ALGLIB --
+             Copyright 02.12.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void polynomialbuild(double[] x,
+            double[] y,
+            int n,
+            ratint.barycentricinterpolant p)
+        {
+            int j = 0;
+            int k = 0;
+            double[] w = new double[0];
+            double b = 0;
+            double a = 0;
+            double v = 0;
+            double mx = 0;
+            double[] sortrbuf = new double[0];
+            double[] sortrbuf2 = new double[0];
+            int i_ = 0;
+
+            x = (double[])x.Clone();
+            y = (double[])y.Clone();
+
+            alglib.ap.assert(n>0, "PolynomialBuild: N<=0!");
+            alglib.ap.assert(alglib.ap.len(x)>=n, "PolynomialBuild: Length(X)<N!");
+            alglib.ap.assert(alglib.ap.len(y)>=n, "PolynomialBuild: Length(Y)<N!");
+            alglib.ap.assert(apserv.isfinitevector(x, n), "PolynomialBuild: X contains infinite or NaN values!");
+            alglib.ap.assert(apserv.isfinitevector(y, n), "PolynomialBuild: Y contains infinite or NaN values!");
+            tsort.tagsortfastr(ref x, ref y, ref sortrbuf, ref sortrbuf2, n);
+            alglib.ap.assert(apserv.aredistinct(x, n), "PolynomialBuild: at least two consequent points are too close!");
+            
+            //
+            // calculate W[j]
+            // multi-pass algorithm is used to avoid overflow
+            //
+            w = new double[n];
+            a = x[0];
+            b = x[0];
+            for(j=0; j<=n-1; j++)
+            {
+                w[j] = 1;
+                a = Math.Min(a, x[j]);
+                b = Math.Max(b, x[j]);
+            }
+            for(k=0; k<=n-1; k++)
+            {
+                
+                //
+                // W[K] is used instead of 0.0 because
+                // cycle on J does not touch K-th element
+                // and we MUST get maximum from ALL elements
+                //
+                mx = Math.Abs(w[k]);
+                for(j=0; j<=n-1; j++)
+                {
+                    if( j!=k )
+                    {
+                        v = (b-a)/(x[j]-x[k]);
+                        w[j] = w[j]*v;
+                        mx = Math.Max(mx, Math.Abs(w[j]));
+                    }
+                }
+                if( k%5==0 )
+                {
+                    
+                    //
+                    // every 5-th run we renormalize W[]
+                    //
+                    v = 1/mx;
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        w[i_] = v*w[i_];
+                    }
+                }
+            }
+            ratint.barycentricbuildxyw(x, y, w, n, p);
+        }
+
+
+        /*************************************************************************
+        Lagrange intepolant: generation of the model on equidistant grid.
+        This function has O(N) complexity.
+
+        INPUT PARAMETERS:
+            A   -   left boundary of [A,B]
+            B   -   right boundary of [A,B]
+            Y   -   function values at the nodes, array[0..N-1]
+            N   -   number of points, N>=1
+                    for N=1 a constant model is constructed.
+
+        OUTPUT PARAMETERS
+            P   -   barycentric model which represents Lagrange interpolant
+                    (see ratint unit info and BarycentricCalc() description for
+                    more information).
+
+          -- ALGLIB --
+             Copyright 03.12.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void polynomialbuildeqdist(double a,
+            double b,
+            double[] y,
+            int n,
+            ratint.barycentricinterpolant p)
+        {
+            int i = 0;
+            double[] w = new double[0];
+            double[] x = new double[0];
+            double v = 0;
+
+            alglib.ap.assert(n>0, "PolynomialBuildEqDist: N<=0!");
+            alglib.ap.assert(alglib.ap.len(y)>=n, "PolynomialBuildEqDist: Length(Y)<N!");
+            alglib.ap.assert(math.isfinite(a), "PolynomialBuildEqDist: A is infinite or NaN!");
+            alglib.ap.assert(math.isfinite(b), "PolynomialBuildEqDist: B is infinite or NaN!");
+            alglib.ap.assert(apserv.isfinitevector(y, n), "PolynomialBuildEqDist: Y contains infinite or NaN values!");
+            alglib.ap.assert((double)(b)!=(double)(a), "PolynomialBuildEqDist: B=A!");
+            alglib.ap.assert((double)(a+(b-a)/n)!=(double)(a), "PolynomialBuildEqDist: B is too close to A!");
+            
+            //
+            // Special case: N=1
+            //
+            if( n==1 )
+            {
+                x = new double[1];
+                w = new double[1];
+                x[0] = 0.5*(b+a);
+                w[0] = 1;
+                ratint.barycentricbuildxyw(x, y, w, 1, p);
+                return;
+            }
+            
+            //
+            // general case
+            //
+            x = new double[n];
+            w = new double[n];
+            v = 1;
+            for(i=0; i<=n-1; i++)
+            {
+                w[i] = v;
+                x[i] = a+(b-a)*i/(n-1);
+                v = -(v*(n-1-i));
+                v = v/(i+1);
+            }
+            ratint.barycentricbuildxyw(x, y, w, n, p);
+        }
+
+
+        /*************************************************************************
+        Lagrange intepolant on Chebyshev grid (first kind).
+        This function has O(N) complexity.
+
+        INPUT PARAMETERS:
+            A   -   left boundary of [A,B]
+            B   -   right boundary of [A,B]
+            Y   -   function values at the nodes, array[0..N-1],
+                    Y[I] = Y(0.5*(B+A) + 0.5*(B-A)*Cos(PI*(2*i+1)/(2*n)))
+            N   -   number of points, N>=1
+                    for N=1 a constant model is constructed.
+
+        OUTPUT PARAMETERS
+            P   -   barycentric model which represents Lagrange interpolant
+                    (see ratint unit info and BarycentricCalc() description for
+                    more information).
+
+          -- ALGLIB --
+             Copyright 03.12.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void polynomialbuildcheb1(double a,
+            double b,
+            double[] y,
+            int n,
+            ratint.barycentricinterpolant p)
+        {
+            int i = 0;
+            double[] w = new double[0];
+            double[] x = new double[0];
+            double v = 0;
+            double t = 0;
+
+            alglib.ap.assert(n>0, "PolynomialBuildCheb1: N<=0!");
+            alglib.ap.assert(alglib.ap.len(y)>=n, "PolynomialBuildCheb1: Length(Y)<N!");
+            alglib.ap.assert(math.isfinite(a), "PolynomialBuildCheb1: A is infinite or NaN!");
+            alglib.ap.assert(math.isfinite(b), "PolynomialBuildCheb1: B is infinite or NaN!");
+            alglib.ap.assert(apserv.isfinitevector(y, n), "PolynomialBuildCheb1: Y contains infinite or NaN values!");
+            alglib.ap.assert((double)(b)!=(double)(a), "PolynomialBuildCheb1: B=A!");
+            
+            //
+            // Special case: N=1
+            //
+            if( n==1 )
+            {
+                x = new double[1];
+                w = new double[1];
+                x[0] = 0.5*(b+a);
+                w[0] = 1;
+                ratint.barycentricbuildxyw(x, y, w, 1, p);
+                return;
+            }
+            
+            //
+            // general case
+            //
+            x = new double[n];
+            w = new double[n];
+            v = 1;
+            for(i=0; i<=n-1; i++)
+            {
+                t = Math.Tan(0.5*Math.PI*(2*i+1)/(2*n));
+                w[i] = 2*v*t/(1+math.sqr(t));
+                x[i] = 0.5*(b+a)+0.5*(b-a)*(1-math.sqr(t))/(1+math.sqr(t));
+                v = -v;
+            }
+            ratint.barycentricbuildxyw(x, y, w, n, p);
+        }
+
+
+        /*************************************************************************
+        Lagrange intepolant on Chebyshev grid (second kind).
+        This function has O(N) complexity.
+
+        INPUT PARAMETERS:
+            A   -   left boundary of [A,B]
+            B   -   right boundary of [A,B]
+            Y   -   function values at the nodes, array[0..N-1],
+                    Y[I] = Y(0.5*(B+A) + 0.5*(B-A)*Cos(PI*i/(n-1)))
+            N   -   number of points, N>=1
+                    for N=1 a constant model is constructed.
+
+        OUTPUT PARAMETERS
+            P   -   barycentric model which represents Lagrange interpolant
+                    (see ratint unit info and BarycentricCalc() description for
+                    more information).
+
+          -- ALGLIB --
+             Copyright 03.12.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void polynomialbuildcheb2(double a,
+            double b,
+            double[] y,
+            int n,
+            ratint.barycentricinterpolant p)
+        {
+            int i = 0;
+            double[] w = new double[0];
+            double[] x = new double[0];
+            double v = 0;
+
+            alglib.ap.assert(n>0, "PolynomialBuildCheb2: N<=0!");
+            alglib.ap.assert(alglib.ap.len(y)>=n, "PolynomialBuildCheb2: Length(Y)<N!");
+            alglib.ap.assert(math.isfinite(a), "PolynomialBuildCheb2: A is infinite or NaN!");
+            alglib.ap.assert(math.isfinite(b), "PolynomialBuildCheb2: B is infinite or NaN!");
+            alglib.ap.assert((double)(b)!=(double)(a), "PolynomialBuildCheb2: B=A!");
+            alglib.ap.assert(apserv.isfinitevector(y, n), "PolynomialBuildCheb2: Y contains infinite or NaN values!");
+            
+            //
+            // Special case: N=1
+            //
+            if( n==1 )
+            {
+                x = new double[1];
+                w = new double[1];
+                x[0] = 0.5*(b+a);
+                w[0] = 1;
+                ratint.barycentricbuildxyw(x, y, w, 1, p);
+                return;
+            }
+            
+            //
+            // general case
+            //
+            x = new double[n];
+            w = new double[n];
+            v = 1;
+            for(i=0; i<=n-1; i++)
+            {
+                if( i==0 || i==n-1 )
+                {
+                    w[i] = v*0.5;
+                }
+                else
+                {
+                    w[i] = v;
+                }
+                x[i] = 0.5*(b+a)+0.5*(b-a)*Math.Cos(Math.PI*i/(n-1));
+                v = -v;
+            }
+            ratint.barycentricbuildxyw(x, y, w, n, p);
+        }
+
+
+        /*************************************************************************
+        Fast equidistant polynomial interpolation function with O(N) complexity
+
+        INPUT PARAMETERS:
+            A   -   left boundary of [A,B]
+            B   -   right boundary of [A,B]
+            F   -   function values, array[0..N-1]
+            N   -   number of points on equidistant grid, N>=1
+                    for N=1 a constant model is constructed.
+            T   -   position where P(x) is calculated
+
+        RESULT
+            value of the Lagrange interpolant at T
+            
+        IMPORTANT
+            this function provides fast interface which is not overflow-safe
+            nor it is very precise.
+            the best option is to use  PolynomialBuildEqDist()/BarycentricCalc()
+            subroutines unless you are pretty sure that your data will not result
+            in overflow.
+
+          -- ALGLIB --
+             Copyright 02.12.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static double polynomialcalceqdist(double a,
+            double b,
+            double[] f,
+            int n,
+            double t)
+        {
+            double result = 0;
+            double s1 = 0;
+            double s2 = 0;
+            double v = 0;
+            double threshold = 0;
+            double s = 0;
+            double h = 0;
+            int i = 0;
+            int j = 0;
+            double w = 0;
+            double x = 0;
+
+            alglib.ap.assert(n>0, "PolynomialCalcEqDist: N<=0!");
+            alglib.ap.assert(alglib.ap.len(f)>=n, "PolynomialCalcEqDist: Length(F)<N!");
+            alglib.ap.assert(math.isfinite(a), "PolynomialCalcEqDist: A is infinite or NaN!");
+            alglib.ap.assert(math.isfinite(b), "PolynomialCalcEqDist: B is infinite or NaN!");
+            alglib.ap.assert(apserv.isfinitevector(f, n), "PolynomialCalcEqDist: F contains infinite or NaN values!");
+            alglib.ap.assert((double)(b)!=(double)(a), "PolynomialCalcEqDist: B=A!");
+            alglib.ap.assert(!Double.IsInfinity(t), "PolynomialCalcEqDist: T is infinite!");
+            
+            //
+            // Special case: T is NAN
+            //
+            if( Double.IsNaN(t) )
+            {
+                result = Double.NaN;
+                return result;
+            }
+            
+            //
+            // Special case: N=1
+            //
+            if( n==1 )
+            {
+                result = f[0];
+                return result;
+            }
+            
+            //
+            // First, decide: should we use "safe" formula (guarded
+            // against overflow) or fast one?
+            //
+            threshold = Math.Sqrt(math.minrealnumber);
+            j = 0;
+            s = t-a;
+            for(i=1; i<=n-1; i++)
+            {
+                x = a+(double)i/(double)(n-1)*(b-a);
+                if( (double)(Math.Abs(t-x))<(double)(Math.Abs(s)) )
+                {
+                    s = t-x;
+                    j = i;
+                }
+            }
+            if( (double)(s)==(double)(0) )
+            {
+                result = f[j];
+                return result;
+            }
+            if( (double)(Math.Abs(s))>(double)(threshold) )
+            {
+                
+                //
+                // use fast formula
+                //
+                j = -1;
+                s = 1.0;
+            }
+            
+            //
+            // Calculate using safe or fast barycentric formula
+            //
+            s1 = 0;
+            s2 = 0;
+            w = 1.0;
+            h = (b-a)/(n-1);
+            for(i=0; i<=n-1; i++)
+            {
+                if( i!=j )
+                {
+                    v = s*w/(t-(a+i*h));
+                    s1 = s1+v*f[i];
+                    s2 = s2+v;
+                }
+                else
+                {
+                    v = w;
+                    s1 = s1+v*f[i];
+                    s2 = s2+v;
+                }
+                w = -(w*(n-1-i));
+                w = w/(i+1);
+            }
+            result = s1/s2;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Fast polynomial interpolation function on Chebyshev points (first kind)
+        with O(N) complexity.
+
+        INPUT PARAMETERS:
+            A   -   left boundary of [A,B]
+            B   -   right boundary of [A,B]
+            F   -   function values, array[0..N-1]
+            N   -   number of points on Chebyshev grid (first kind),
+                    X[i] = 0.5*(B+A) + 0.5*(B-A)*Cos(PI*(2*i+1)/(2*n))
+                    for N=1 a constant model is constructed.
+            T   -   position where P(x) is calculated
+
+        RESULT
+            value of the Lagrange interpolant at T
+
+        IMPORTANT
+            this function provides fast interface which is not overflow-safe
+            nor it is very precise.
+            the best option is to use  PolIntBuildCheb1()/BarycentricCalc()
+            subroutines unless you are pretty sure that your data will not result
+            in overflow.
+
+          -- ALGLIB --
+             Copyright 02.12.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static double polynomialcalccheb1(double a,
+            double b,
+            double[] f,
+            int n,
+            double t)
+        {
+            double result = 0;
+            double s1 = 0;
+            double s2 = 0;
+            double v = 0;
+            double threshold = 0;
+            double s = 0;
+            int i = 0;
+            int j = 0;
+            double a0 = 0;
+            double delta = 0;
+            double alpha = 0;
+            double beta = 0;
+            double ca = 0;
+            double sa = 0;
+            double tempc = 0;
+            double temps = 0;
+            double x = 0;
+            double w = 0;
+            double p1 = 0;
+
+            alglib.ap.assert(n>0, "PolynomialCalcCheb1: N<=0!");
+            alglib.ap.assert(alglib.ap.len(f)>=n, "PolynomialCalcCheb1: Length(F)<N!");
+            alglib.ap.assert(math.isfinite(a), "PolynomialCalcCheb1: A is infinite or NaN!");
+            alglib.ap.assert(math.isfinite(b), "PolynomialCalcCheb1: B is infinite or NaN!");
+            alglib.ap.assert(apserv.isfinitevector(f, n), "PolynomialCalcCheb1: F contains infinite or NaN values!");
+            alglib.ap.assert((double)(b)!=(double)(a), "PolynomialCalcCheb1: B=A!");
+            alglib.ap.assert(!Double.IsInfinity(t), "PolynomialCalcCheb1: T is infinite!");
+            
+            //
+            // Special case: T is NAN
+            //
+            if( Double.IsNaN(t) )
+            {
+                result = Double.NaN;
+                return result;
+            }
+            
+            //
+            // Special case: N=1
+            //
+            if( n==1 )
+            {
+                result = f[0];
+                return result;
+            }
+            
+            //
+            // Prepare information for the recurrence formula
+            // used to calculate sin(pi*(2j+1)/(2n+2)) and
+            // cos(pi*(2j+1)/(2n+2)):
+            //
+            // A0    = pi/(2n+2)
+            // Delta = pi/(n+1)
+            // Alpha = 2 sin^2 (Delta/2)
+            // Beta  = sin(Delta)
+            //
+            // so that sin(..) = sin(A0+j*delta) and cos(..) = cos(A0+j*delta).
+            // Then we use
+            //
+            // sin(x+delta) = sin(x) - (alpha*sin(x) - beta*cos(x))
+            // cos(x+delta) = cos(x) - (alpha*cos(x) - beta*sin(x))
+            //
+            // to repeatedly calculate sin(..) and cos(..).
+            //
+            threshold = Math.Sqrt(math.minrealnumber);
+            t = (t-0.5*(a+b))/(0.5*(b-a));
+            a0 = Math.PI/(2*(n-1)+2);
+            delta = 2*Math.PI/(2*(n-1)+2);
+            alpha = 2*math.sqr(Math.Sin(delta/2));
+            beta = Math.Sin(delta);
+            
+            //
+            // First, decide: should we use "safe" formula (guarded
+            // against overflow) or fast one?
+            //
+            ca = Math.Cos(a0);
+            sa = Math.Sin(a0);
+            j = 0;
+            x = ca;
+            s = t-x;
+            for(i=1; i<=n-1; i++)
+            {
+                
+                //
+                // Next X[i]
+                //
+                temps = sa-(alpha*sa-beta*ca);
+                tempc = ca-(alpha*ca+beta*sa);
+                sa = temps;
+                ca = tempc;
+                x = ca;
+                
+                //
+                // Use X[i]
+                //
+                if( (double)(Math.Abs(t-x))<(double)(Math.Abs(s)) )
+                {
+                    s = t-x;
+                    j = i;
+                }
+            }
+            if( (double)(s)==(double)(0) )
+            {
+                result = f[j];
+                return result;
+            }
+            if( (double)(Math.Abs(s))>(double)(threshold) )
+            {
+                
+                //
+                // use fast formula
+                //
+                j = -1;
+                s = 1.0;
+            }
+            
+            //
+            // Calculate using safe or fast barycentric formula
+            //
+            s1 = 0;
+            s2 = 0;
+            ca = Math.Cos(a0);
+            sa = Math.Sin(a0);
+            p1 = 1.0;
+            for(i=0; i<=n-1; i++)
+            {
+                
+                //
+                // Calculate X[i], W[i]
+                //
+                x = ca;
+                w = p1*sa;
+                
+                //
+                // Proceed
+                //
+                if( i!=j )
+                {
+                    v = s*w/(t-x);
+                    s1 = s1+v*f[i];
+                    s2 = s2+v;
+                }
+                else
+                {
+                    v = w;
+                    s1 = s1+v*f[i];
+                    s2 = s2+v;
+                }
+                
+                //
+                // Next CA, SA, P1
+                //
+                temps = sa-(alpha*sa-beta*ca);
+                tempc = ca-(alpha*ca+beta*sa);
+                sa = temps;
+                ca = tempc;
+                p1 = -p1;
+            }
+            result = s1/s2;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Fast polynomial interpolation function on Chebyshev points (second kind)
+        with O(N) complexity.
+
+        INPUT PARAMETERS:
+            A   -   left boundary of [A,B]
+            B   -   right boundary of [A,B]
+            F   -   function values, array[0..N-1]
+            N   -   number of points on Chebyshev grid (second kind),
+                    X[i] = 0.5*(B+A) + 0.5*(B-A)*Cos(PI*i/(n-1))
+                    for N=1 a constant model is constructed.
+            T   -   position where P(x) is calculated
+
+        RESULT
+            value of the Lagrange interpolant at T
+
+        IMPORTANT
+            this function provides fast interface which is not overflow-safe
+            nor it is very precise.
+            the best option is to use PolIntBuildCheb2()/BarycentricCalc()
+            subroutines unless you are pretty sure that your data will not result
+            in overflow.
+
+          -- ALGLIB --
+             Copyright 02.12.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static double polynomialcalccheb2(double a,
+            double b,
+            double[] f,
+            int n,
+            double t)
+        {
+            double result = 0;
+            double s1 = 0;
+            double s2 = 0;
+            double v = 0;
+            double threshold = 0;
+            double s = 0;
+            int i = 0;
+            int j = 0;
+            double a0 = 0;
+            double delta = 0;
+            double alpha = 0;
+            double beta = 0;
+            double ca = 0;
+            double sa = 0;
+            double tempc = 0;
+            double temps = 0;
+            double x = 0;
+            double w = 0;
+            double p1 = 0;
+
+            alglib.ap.assert(n>0, "PolynomialCalcCheb2: N<=0!");
+            alglib.ap.assert(alglib.ap.len(f)>=n, "PolynomialCalcCheb2: Length(F)<N!");
+            alglib.ap.assert(math.isfinite(a), "PolynomialCalcCheb2: A is infinite or NaN!");
+            alglib.ap.assert(math.isfinite(b), "PolynomialCalcCheb2: B is infinite or NaN!");
+            alglib.ap.assert((double)(b)!=(double)(a), "PolynomialCalcCheb2: B=A!");
+            alglib.ap.assert(apserv.isfinitevector(f, n), "PolynomialCalcCheb2: F contains infinite or NaN values!");
+            alglib.ap.assert(!Double.IsInfinity(t), "PolynomialCalcEqDist: T is infinite!");
+            
+            //
+            // Special case: T is NAN
+            //
+            if( Double.IsNaN(t) )
+            {
+                result = Double.NaN;
+                return result;
+            }
+            
+            //
+            // Special case: N=1
+            //
+            if( n==1 )
+            {
+                result = f[0];
+                return result;
+            }
+            
+            //
+            // Prepare information for the recurrence formula
+            // used to calculate sin(pi*i/n) and
+            // cos(pi*i/n):
+            //
+            // A0    = 0
+            // Delta = pi/n
+            // Alpha = 2 sin^2 (Delta/2)
+            // Beta  = sin(Delta)
+            //
+            // so that sin(..) = sin(A0+j*delta) and cos(..) = cos(A0+j*delta).
+            // Then we use
+            //
+            // sin(x+delta) = sin(x) - (alpha*sin(x) - beta*cos(x))
+            // cos(x+delta) = cos(x) - (alpha*cos(x) - beta*sin(x))
+            //
+            // to repeatedly calculate sin(..) and cos(..).
+            //
+            threshold = Math.Sqrt(math.minrealnumber);
+            t = (t-0.5*(a+b))/(0.5*(b-a));
+            a0 = 0.0;
+            delta = Math.PI/(n-1);
+            alpha = 2*math.sqr(Math.Sin(delta/2));
+            beta = Math.Sin(delta);
+            
+            //
+            // First, decide: should we use "safe" formula (guarded
+            // against overflow) or fast one?
+            //
+            ca = Math.Cos(a0);
+            sa = Math.Sin(a0);
+            j = 0;
+            x = ca;
+            s = t-x;
+            for(i=1; i<=n-1; i++)
+            {
+                
+                //
+                // Next X[i]
+                //
+                temps = sa-(alpha*sa-beta*ca);
+                tempc = ca-(alpha*ca+beta*sa);
+                sa = temps;
+                ca = tempc;
+                x = ca;
+                
+                //
+                // Use X[i]
+                //
+                if( (double)(Math.Abs(t-x))<(double)(Math.Abs(s)) )
+                {
+                    s = t-x;
+                    j = i;
+                }
+            }
+            if( (double)(s)==(double)(0) )
+            {
+                result = f[j];
+                return result;
+            }
+            if( (double)(Math.Abs(s))>(double)(threshold) )
+            {
+                
+                //
+                // use fast formula
+                //
+                j = -1;
+                s = 1.0;
+            }
+            
+            //
+            // Calculate using safe or fast barycentric formula
+            //
+            s1 = 0;
+            s2 = 0;
+            ca = Math.Cos(a0);
+            sa = Math.Sin(a0);
+            p1 = 1.0;
+            for(i=0; i<=n-1; i++)
+            {
+                
+                //
+                // Calculate X[i], W[i]
+                //
+                x = ca;
+                if( i==0 || i==n-1 )
+                {
+                    w = 0.5*p1;
+                }
+                else
+                {
+                    w = 1.0*p1;
+                }
+                
+                //
+                // Proceed
+                //
+                if( i!=j )
+                {
+                    v = s*w/(t-x);
+                    s1 = s1+v*f[i];
+                    s2 = s2+v;
+                }
+                else
+                {
+                    v = w;
+                    s1 = s1+v*f[i];
+                    s2 = s2+v;
+                }
+                
+                //
+                // Next CA, SA, P1
+                //
+                temps = sa-(alpha*sa-beta*ca);
+                tempc = ca-(alpha*ca+beta*sa);
+                sa = temps;
+                ca = tempc;
+                p1 = -p1;
+            }
+            result = s1/s2;
+            return result;
+        }
+
+
+    }
+    public class spline1d
+    {
+        /*************************************************************************
+        1-dimensional spline interpolant
+        *************************************************************************/
+        public class spline1dinterpolant
+        {
+            public bool periodic;
+            public int n;
+            public int k;
+            public int continuity;
+            public double[] x;
+            public double[] c;
+            public spline1dinterpolant()
+            {
+                x = new double[0];
+                c = new double[0];
+            }
+        };
+
+
+
+
+        /*************************************************************************
+        This subroutine builds linear spline interpolant
+
+        INPUT PARAMETERS:
+            X   -   spline nodes, array[0..N-1]
+            Y   -   function values, array[0..N-1]
+            N   -   points count (optional):
+                    * N>=2
+                    * if given, only first N points are used to build spline
+                    * if not given, automatically detected from X/Y sizes
+                      (len(X) must be equal to len(Y))
+            
+        OUTPUT PARAMETERS:
+            C   -   spline interpolant
+
+
+        ORDER OF POINTS
+
+        Subroutine automatically sorts points, so caller may pass unsorted array.
+
+          -- ALGLIB PROJECT --
+             Copyright 24.06.2007 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spline1dbuildlinear(double[] x,
+            double[] y,
+            int n,
+            spline1dinterpolant c)
+        {
+            int i = 0;
+
+            x = (double[])x.Clone();
+            y = (double[])y.Clone();
+
+            alglib.ap.assert(n>1, "Spline1DBuildLinear: N<2!");
+            alglib.ap.assert(alglib.ap.len(x)>=n, "Spline1DBuildLinear: Length(X)<N!");
+            alglib.ap.assert(alglib.ap.len(y)>=n, "Spline1DBuildLinear: Length(Y)<N!");
+            
+            //
+            // check and sort points
+            //
+            alglib.ap.assert(apserv.isfinitevector(x, n), "Spline1DBuildLinear: X contains infinite or NAN values!");
+            alglib.ap.assert(apserv.isfinitevector(y, n), "Spline1DBuildLinear: Y contains infinite or NAN values!");
+            heapsortpoints(ref x, ref y, n);
+            alglib.ap.assert(apserv.aredistinct(x, n), "Spline1DBuildLinear: at least two consequent points are too close!");
+            
+            //
+            // Build
+            //
+            c.periodic = false;
+            c.n = n;
+            c.k = 3;
+            c.continuity = 0;
+            c.x = new double[n];
+            c.c = new double[4*(n-1)+2];
+            for(i=0; i<=n-1; i++)
+            {
+                c.x[i] = x[i];
+            }
+            for(i=0; i<=n-2; i++)
+            {
+                c.c[4*i+0] = y[i];
+                c.c[4*i+1] = (y[i+1]-y[i])/(x[i+1]-x[i]);
+                c.c[4*i+2] = 0;
+                c.c[4*i+3] = 0;
+            }
+            c.c[4*(n-1)+0] = y[n-1];
+            c.c[4*(n-1)+1] = c.c[4*(n-2)+1];
+        }
+
+
+        /*************************************************************************
+        This subroutine builds cubic spline interpolant.
+
+        INPUT PARAMETERS:
+            X           -   spline nodes, array[0..N-1].
+            Y           -   function values, array[0..N-1].
+            
+        OPTIONAL PARAMETERS:
+            N           -   points count:
+                            * N>=2
+                            * if given, only first N points are used to build spline
+                            * if not given, automatically detected from X/Y sizes
+                              (len(X) must be equal to len(Y))
+            BoundLType  -   boundary condition type for the left boundary
+            BoundL      -   left boundary condition (first or second derivative,
+                            depending on the BoundLType)
+            BoundRType  -   boundary condition type for the right boundary
+            BoundR      -   right boundary condition (first or second derivative,
+                            depending on the BoundRType)
+
+        OUTPUT PARAMETERS:
+            C           -   spline interpolant
+
+        ORDER OF POINTS
+
+        Subroutine automatically sorts points, so caller may pass unsorted array.
+
+        SETTING BOUNDARY VALUES:
+
+        The BoundLType/BoundRType parameters can have the following values:
+            * -1, which corresonds to the periodic (cyclic) boundary conditions.
+                  In this case:
+                  * both BoundLType and BoundRType must be equal to -1.
+                  * BoundL/BoundR are ignored
+                  * Y[last] is ignored (it is assumed to be equal to Y[first]).
+            *  0, which  corresponds  to  the  parabolically   terminated  spline
+                  (BoundL and/or BoundR are ignored).
+            *  1, which corresponds to the first derivative boundary condition
+            *  2, which corresponds to the second derivative boundary condition
+            *  by default, BoundType=0 is used
+
+        PROBLEMS WITH PERIODIC BOUNDARY CONDITIONS:
+
+        Problems with periodic boundary conditions have Y[first_point]=Y[last_point].
+        However, this subroutine doesn't require you to specify equal  values  for
+        the first and last points - it automatically forces them  to  be  equal by
+        copying  Y[first_point]  (corresponds  to the leftmost,  minimal  X[])  to
+        Y[last_point]. However it is recommended to pass consistent values of Y[],
+        i.e. to make Y[first_point]=Y[last_point].
+
+          -- ALGLIB PROJECT --
+             Copyright 23.06.2007 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spline1dbuildcubic(double[] x,
+            double[] y,
+            int n,
+            int boundltype,
+            double boundl,
+            int boundrtype,
+            double boundr,
+            spline1dinterpolant c)
+        {
+            double[] a1 = new double[0];
+            double[] a2 = new double[0];
+            double[] a3 = new double[0];
+            double[] b = new double[0];
+            double[] dt = new double[0];
+            double[] d = new double[0];
+            int[] p = new int[0];
+            int ylen = 0;
+
+            x = (double[])x.Clone();
+            y = (double[])y.Clone();
+
+            
+            //
+            // check correctness of boundary conditions
+            //
+            alglib.ap.assert(((boundltype==-1 || boundltype==0) || boundltype==1) || boundltype==2, "Spline1DBuildCubic: incorrect BoundLType!");
+            alglib.ap.assert(((boundrtype==-1 || boundrtype==0) || boundrtype==1) || boundrtype==2, "Spline1DBuildCubic: incorrect BoundRType!");
+            alglib.ap.assert((boundrtype==-1 && boundltype==-1) || (boundrtype!=-1 && boundltype!=-1), "Spline1DBuildCubic: incorrect BoundLType/BoundRType!");
+            if( boundltype==1 || boundltype==2 )
+            {
+                alglib.ap.assert(math.isfinite(boundl), "Spline1DBuildCubic: BoundL is infinite or NAN!");
+            }
+            if( boundrtype==1 || boundrtype==2 )
+            {
+                alglib.ap.assert(math.isfinite(boundr), "Spline1DBuildCubic: BoundR is infinite or NAN!");
+            }
+            
+            //
+            // check lengths of arguments
+            //
+            alglib.ap.assert(n>=2, "Spline1DBuildCubic: N<2!");
+            alglib.ap.assert(alglib.ap.len(x)>=n, "Spline1DBuildCubic: Length(X)<N!");
+            alglib.ap.assert(alglib.ap.len(y)>=n, "Spline1DBuildCubic: Length(Y)<N!");
+            
+            //
+            // check and sort points
+            //
+            ylen = n;
+            if( boundltype==-1 )
+            {
+                ylen = n-1;
+            }
+            alglib.ap.assert(apserv.isfinitevector(x, n), "Spline1DBuildCubic: X contains infinite or NAN values!");
+            alglib.ap.assert(apserv.isfinitevector(y, ylen), "Spline1DBuildCubic: Y contains infinite or NAN values!");
+            heapsortppoints(ref x, ref y, ref p, n);
+            alglib.ap.assert(apserv.aredistinct(x, n), "Spline1DBuildCubic: at least two consequent points are too close!");
+            
+            //
+            // Now we've checked and preordered everything,
+            // so we can call internal function to calculate derivatives,
+            // and then build Hermite spline using these derivatives
+            //
+            if( boundltype==-1 || boundrtype==-1 )
+            {
+                y[n-1] = y[0];
+            }
+            spline1dgriddiffcubicinternal(x, ref y, n, boundltype, boundl, boundrtype, boundr, ref d, ref a1, ref a2, ref a3, ref b, ref dt);
+            spline1dbuildhermite(x, y, d, n, c);
+            c.periodic = boundltype==-1 || boundrtype==-1;
+            c.continuity = 2;
+        }
+
+
+        /*************************************************************************
+        This function solves following problem: given table y[] of function values
+        at nodes x[], it calculates and returns table of function derivatives  d[]
+        (calculated at the same nodes x[]).
+
+        This function yields same result as Spline1DBuildCubic() call followed  by
+        sequence of Spline1DDiff() calls, but it can be several times faster  when
+        called for ordered X[] and X2[].
+
+        INPUT PARAMETERS:
+            X           -   spline nodes
+            Y           -   function values
+
+        OPTIONAL PARAMETERS:
+            N           -   points count:
+                            * N>=2
+                            * if given, only first N points are used
+                            * if not given, automatically detected from X/Y sizes
+                              (len(X) must be equal to len(Y))
+            BoundLType  -   boundary condition type for the left boundary
+            BoundL      -   left boundary condition (first or second derivative,
+                            depending on the BoundLType)
+            BoundRType  -   boundary condition type for the right boundary
+            BoundR      -   right boundary condition (first or second derivative,
+                            depending on the BoundRType)
+
+        OUTPUT PARAMETERS:
+            D           -   derivative values at X[]
+
+        ORDER OF POINTS
+
+        Subroutine automatically sorts points, so caller may pass unsorted array.
+        Derivative values are correctly reordered on return, so  D[I]  is  always
+        equal to S'(X[I]) independently of points order.
+
+        SETTING BOUNDARY VALUES:
+
+        The BoundLType/BoundRType parameters can have the following values:
+            * -1, which corresonds to the periodic (cyclic) boundary conditions.
+                  In this case:
+                  * both BoundLType and BoundRType must be equal to -1.
+                  * BoundL/BoundR are ignored
+                  * Y[last] is ignored (it is assumed to be equal to Y[first]).
+            *  0, which  corresponds  to  the  parabolically   terminated  spline
+                  (BoundL and/or BoundR are ignored).
+            *  1, which corresponds to the first derivative boundary condition
+            *  2, which corresponds to the second derivative boundary condition
+            *  by default, BoundType=0 is used
+
+        PROBLEMS WITH PERIODIC BOUNDARY CONDITIONS:
+
+        Problems with periodic boundary conditions have Y[first_point]=Y[last_point].
+        However, this subroutine doesn't require you to specify equal  values  for
+        the first and last points - it automatically forces them  to  be  equal by
+        copying  Y[first_point]  (corresponds  to the leftmost,  minimal  X[])  to
+        Y[last_point]. However it is recommended to pass consistent values of Y[],
+        i.e. to make Y[first_point]=Y[last_point].
+
+          -- ALGLIB PROJECT --
+             Copyright 03.09.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spline1dgriddiffcubic(double[] x,
+            double[] y,
+            int n,
+            int boundltype,
+            double boundl,
+            int boundrtype,
+            double boundr,
+            ref double[] d)
+        {
+            double[] a1 = new double[0];
+            double[] a2 = new double[0];
+            double[] a3 = new double[0];
+            double[] b = new double[0];
+            double[] dt = new double[0];
+            int[] p = new int[0];
+            int i = 0;
+            int ylen = 0;
+            int i_ = 0;
+
+            x = (double[])x.Clone();
+            y = (double[])y.Clone();
+            d = new double[0];
+
+            
+            //
+            // check correctness of boundary conditions
+            //
+            alglib.ap.assert(((boundltype==-1 || boundltype==0) || boundltype==1) || boundltype==2, "Spline1DGridDiffCubic: incorrect BoundLType!");
+            alglib.ap.assert(((boundrtype==-1 || boundrtype==0) || boundrtype==1) || boundrtype==2, "Spline1DGridDiffCubic: incorrect BoundRType!");
+            alglib.ap.assert((boundrtype==-1 && boundltype==-1) || (boundrtype!=-1 && boundltype!=-1), "Spline1DGridDiffCubic: incorrect BoundLType/BoundRType!");
+            if( boundltype==1 || boundltype==2 )
+            {
+                alglib.ap.assert(math.isfinite(boundl), "Spline1DGridDiffCubic: BoundL is infinite or NAN!");
+            }
+            if( boundrtype==1 || boundrtype==2 )
+            {
+                alglib.ap.assert(math.isfinite(boundr), "Spline1DGridDiffCubic: BoundR is infinite or NAN!");
+            }
+            
+            //
+            // check lengths of arguments
+            //
+            alglib.ap.assert(n>=2, "Spline1DGridDiffCubic: N<2!");
+            alglib.ap.assert(alglib.ap.len(x)>=n, "Spline1DGridDiffCubic: Length(X)<N!");
+            alglib.ap.assert(alglib.ap.len(y)>=n, "Spline1DGridDiffCubic: Length(Y)<N!");
+            
+            //
+            // check and sort points
+            //
+            ylen = n;
+            if( boundltype==-1 )
+            {
+                ylen = n-1;
+            }
+            alglib.ap.assert(apserv.isfinitevector(x, n), "Spline1DGridDiffCubic: X contains infinite or NAN values!");
+            alglib.ap.assert(apserv.isfinitevector(y, ylen), "Spline1DGridDiffCubic: Y contains infinite or NAN values!");
+            heapsortppoints(ref x, ref y, ref p, n);
+            alglib.ap.assert(apserv.aredistinct(x, n), "Spline1DGridDiffCubic: at least two consequent points are too close!");
+            
+            //
+            // Now we've checked and preordered everything,
+            // so we can call internal function.
+            //
+            spline1dgriddiffcubicinternal(x, ref y, n, boundltype, boundl, boundrtype, boundr, ref d, ref a1, ref a2, ref a3, ref b, ref dt);
+            
+            //
+            // Remember that HeapSortPPoints() call?
+            // Now we have to reorder them back.
+            //
+            if( alglib.ap.len(dt)<n )
+            {
+                dt = new double[n];
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                dt[p[i]] = d[i];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                d[i_] = dt[i_];
+            }
+        }
+
+
+        /*************************************************************************
+        This function solves following problem: given table y[] of function values
+        at  nodes  x[],  it  calculates  and  returns  tables  of first and second
+        function derivatives d1[] and d2[] (calculated at the same nodes x[]).
+
+        This function yields same result as Spline1DBuildCubic() call followed  by
+        sequence of Spline1DDiff() calls, but it can be several times faster  when
+        called for ordered X[] and X2[].
+
+        INPUT PARAMETERS:
+            X           -   spline nodes
+            Y           -   function values
+
+        OPTIONAL PARAMETERS:
+            N           -   points count:
+                            * N>=2
+                            * if given, only first N points are used
+                            * if not given, automatically detected from X/Y sizes
+                              (len(X) must be equal to len(Y))
+            BoundLType  -   boundary condition type for the left boundary
+            BoundL      -   left boundary condition (first or second derivative,
+                            depending on the BoundLType)
+            BoundRType  -   boundary condition type for the right boundary
+            BoundR      -   right boundary condition (first or second derivative,
+                            depending on the BoundRType)
+
+        OUTPUT PARAMETERS:
+            D1          -   S' values at X[]
+            D2          -   S'' values at X[]
+
+        ORDER OF POINTS
+
+        Subroutine automatically sorts points, so caller may pass unsorted array.
+        Derivative values are correctly reordered on return, so  D[I]  is  always
+        equal to S'(X[I]) independently of points order.
+
+        SETTING BOUNDARY VALUES:
+
+        The BoundLType/BoundRType parameters can have the following values:
+            * -1, which corresonds to the periodic (cyclic) boundary conditions.
+                  In this case:
+                  * both BoundLType and BoundRType must be equal to -1.
+                  * BoundL/BoundR are ignored
+                  * Y[last] is ignored (it is assumed to be equal to Y[first]).
+            *  0, which  corresponds  to  the  parabolically   terminated  spline
+                  (BoundL and/or BoundR are ignored).
+            *  1, which corresponds to the first derivative boundary condition
+            *  2, which corresponds to the second derivative boundary condition
+            *  by default, BoundType=0 is used
+
+        PROBLEMS WITH PERIODIC BOUNDARY CONDITIONS:
+
+        Problems with periodic boundary conditions have Y[first_point]=Y[last_point].
+        However, this subroutine doesn't require you to specify equal  values  for
+        the first and last points - it automatically forces them  to  be  equal by
+        copying  Y[first_point]  (corresponds  to the leftmost,  minimal  X[])  to
+        Y[last_point]. However it is recommended to pass consistent values of Y[],
+        i.e. to make Y[first_point]=Y[last_point].
+
+          -- ALGLIB PROJECT --
+             Copyright 03.09.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spline1dgriddiff2cubic(double[] x,
+            double[] y,
+            int n,
+            int boundltype,
+            double boundl,
+            int boundrtype,
+            double boundr,
+            ref double[] d1,
+            ref double[] d2)
+        {
+            double[] a1 = new double[0];
+            double[] a2 = new double[0];
+            double[] a3 = new double[0];
+            double[] b = new double[0];
+            double[] dt = new double[0];
+            int[] p = new int[0];
+            int i = 0;
+            int ylen = 0;
+            double delta = 0;
+            double delta2 = 0;
+            double delta3 = 0;
+            double s0 = 0;
+            double s1 = 0;
+            double s2 = 0;
+            double s3 = 0;
+            int i_ = 0;
+
+            x = (double[])x.Clone();
+            y = (double[])y.Clone();
+            d1 = new double[0];
+            d2 = new double[0];
+
+            
+            //
+            // check correctness of boundary conditions
+            //
+            alglib.ap.assert(((boundltype==-1 || boundltype==0) || boundltype==1) || boundltype==2, "Spline1DGridDiff2Cubic: incorrect BoundLType!");
+            alglib.ap.assert(((boundrtype==-1 || boundrtype==0) || boundrtype==1) || boundrtype==2, "Spline1DGridDiff2Cubic: incorrect BoundRType!");
+            alglib.ap.assert((boundrtype==-1 && boundltype==-1) || (boundrtype!=-1 && boundltype!=-1), "Spline1DGridDiff2Cubic: incorrect BoundLType/BoundRType!");
+            if( boundltype==1 || boundltype==2 )
+            {
+                alglib.ap.assert(math.isfinite(boundl), "Spline1DGridDiff2Cubic: BoundL is infinite or NAN!");
+            }
+            if( boundrtype==1 || boundrtype==2 )
+            {
+                alglib.ap.assert(math.isfinite(boundr), "Spline1DGridDiff2Cubic: BoundR is infinite or NAN!");
+            }
+            
+            //
+            // check lengths of arguments
+            //
+            alglib.ap.assert(n>=2, "Spline1DGridDiff2Cubic: N<2!");
+            alglib.ap.assert(alglib.ap.len(x)>=n, "Spline1DGridDiff2Cubic: Length(X)<N!");
+            alglib.ap.assert(alglib.ap.len(y)>=n, "Spline1DGridDiff2Cubic: Length(Y)<N!");
+            
+            //
+            // check and sort points
+            //
+            ylen = n;
+            if( boundltype==-1 )
+            {
+                ylen = n-1;
+            }
+            alglib.ap.assert(apserv.isfinitevector(x, n), "Spline1DGridDiff2Cubic: X contains infinite or NAN values!");
+            alglib.ap.assert(apserv.isfinitevector(y, ylen), "Spline1DGridDiff2Cubic: Y contains infinite or NAN values!");
+            heapsortppoints(ref x, ref y, ref p, n);
+            alglib.ap.assert(apserv.aredistinct(x, n), "Spline1DGridDiff2Cubic: at least two consequent points are too close!");
+            
+            //
+            // Now we've checked and preordered everything,
+            // so we can call internal function.
+            //
+            // After this call we will calculate second derivatives
+            // (manually, by converting to the power basis)
+            //
+            spline1dgriddiffcubicinternal(x, ref y, n, boundltype, boundl, boundrtype, boundr, ref d1, ref a1, ref a2, ref a3, ref b, ref dt);
+            d2 = new double[n];
+            delta = 0;
+            s2 = 0;
+            s3 = 0;
+            for(i=0; i<=n-2; i++)
+            {
+                
+                //
+                // We convert from Hermite basis to the power basis.
+                // Si is coefficient before x^i.
+                //
+                // Inside this cycle we need just S2,
+                // because we calculate S'' exactly at spline node,
+                // (only x^2 matters at x=0), but after iterations
+                // will be over, we will need other coefficients
+                // to calculate spline value at the last node.
+                //
+                delta = x[i+1]-x[i];
+                delta2 = math.sqr(delta);
+                delta3 = delta*delta2;
+                s0 = y[i];
+                s1 = d1[i];
+                s2 = (3*(y[i+1]-y[i])-2*d1[i]*delta-d1[i+1]*delta)/delta2;
+                s3 = (2*(y[i]-y[i+1])+d1[i]*delta+d1[i+1]*delta)/delta3;
+                d2[i] = 2*s2;
+            }
+            d2[n-1] = 2*s2+6*s3*delta;
+            
+            //
+            // Remember that HeapSortPPoints() call?
+            // Now we have to reorder them back.
+            //
+            if( alglib.ap.len(dt)<n )
+            {
+                dt = new double[n];
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                dt[p[i]] = d1[i];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                d1[i_] = dt[i_];
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                dt[p[i]] = d2[i];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                d2[i_] = dt[i_];
+            }
+        }
+
+
+        /*************************************************************************
+        This function solves following problem: given table y[] of function values
+        at old nodes x[]  and new nodes  x2[],  it calculates and returns table of
+        function values y2[] (calculated at x2[]).
+
+        This function yields same result as Spline1DBuildCubic() call followed  by
+        sequence of Spline1DDiff() calls, but it can be several times faster  when
+        called for ordered X[] and X2[].
+
+        INPUT PARAMETERS:
+            X           -   old spline nodes
+            Y           -   function values
+            X2           -  new spline nodes
+
+        OPTIONAL PARAMETERS:
+            N           -   points count:
+                            * N>=2
+                            * if given, only first N points from X/Y are used
+                            * if not given, automatically detected from X/Y sizes
+                              (len(X) must be equal to len(Y))
+            BoundLType  -   boundary condition type for the left boundary
+            BoundL      -   left boundary condition (first or second derivative,
+                            depending on the BoundLType)
+            BoundRType  -   boundary condition type for the right boundary
+            BoundR      -   right boundary condition (first or second derivative,
+                            depending on the BoundRType)
+            N2          -   new points count:
+                            * N2>=2
+                            * if given, only first N2 points from X2 are used
+                            * if not given, automatically detected from X2 size
+
+        OUTPUT PARAMETERS:
+            F2          -   function values at X2[]
+
+        ORDER OF POINTS
+
+        Subroutine automatically sorts points, so caller  may pass unsorted array.
+        Function  values  are correctly reordered on  return, so F2[I]  is  always
+        equal to S(X2[I]) independently of points order.
+
+        SETTING BOUNDARY VALUES:
+
+        The BoundLType/BoundRType parameters can have the following values:
+            * -1, which corresonds to the periodic (cyclic) boundary conditions.
+                  In this case:
+                  * both BoundLType and BoundRType must be equal to -1.
+                  * BoundL/BoundR are ignored
+                  * Y[last] is ignored (it is assumed to be equal to Y[first]).
+            *  0, which  corresponds  to  the  parabolically   terminated  spline
+                  (BoundL and/or BoundR are ignored).
+            *  1, which corresponds to the first derivative boundary condition
+            *  2, which corresponds to the second derivative boundary condition
+            *  by default, BoundType=0 is used
+
+        PROBLEMS WITH PERIODIC BOUNDARY CONDITIONS:
+
+        Problems with periodic boundary conditions have Y[first_point]=Y[last_point].
+        However, this subroutine doesn't require you to specify equal  values  for
+        the first and last points - it automatically forces them  to  be  equal by
+        copying  Y[first_point]  (corresponds  to the leftmost,  minimal  X[])  to
+        Y[last_point]. However it is recommended to pass consistent values of Y[],
+        i.e. to make Y[first_point]=Y[last_point].
+
+          -- ALGLIB PROJECT --
+             Copyright 03.09.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spline1dconvcubic(double[] x,
+            double[] y,
+            int n,
+            int boundltype,
+            double boundl,
+            int boundrtype,
+            double boundr,
+            double[] x2,
+            int n2,
+            ref double[] y2)
+        {
+            double[] a1 = new double[0];
+            double[] a2 = new double[0];
+            double[] a3 = new double[0];
+            double[] b = new double[0];
+            double[] d = new double[0];
+            double[] dt = new double[0];
+            double[] d1 = new double[0];
+            double[] d2 = new double[0];
+            int[] p = new int[0];
+            int[] p2 = new int[0];
+            int i = 0;
+            int ylen = 0;
+            double t = 0;
+            double t2 = 0;
+            int i_ = 0;
+
+            x = (double[])x.Clone();
+            y = (double[])y.Clone();
+            x2 = (double[])x2.Clone();
+            y2 = new double[0];
+
+            
+            //
+            // check correctness of boundary conditions
+            //
+            alglib.ap.assert(((boundltype==-1 || boundltype==0) || boundltype==1) || boundltype==2, "Spline1DConvCubic: incorrect BoundLType!");
+            alglib.ap.assert(((boundrtype==-1 || boundrtype==0) || boundrtype==1) || boundrtype==2, "Spline1DConvCubic: incorrect BoundRType!");
+            alglib.ap.assert((boundrtype==-1 && boundltype==-1) || (boundrtype!=-1 && boundltype!=-1), "Spline1DConvCubic: incorrect BoundLType/BoundRType!");
+            if( boundltype==1 || boundltype==2 )
+            {
+                alglib.ap.assert(math.isfinite(boundl), "Spline1DConvCubic: BoundL is infinite or NAN!");
+            }
+            if( boundrtype==1 || boundrtype==2 )
+            {
+                alglib.ap.assert(math.isfinite(boundr), "Spline1DConvCubic: BoundR is infinite or NAN!");
+            }
+            
+            //
+            // check lengths of arguments
+            //
+            alglib.ap.assert(n>=2, "Spline1DConvCubic: N<2!");
+            alglib.ap.assert(alglib.ap.len(x)>=n, "Spline1DConvCubic: Length(X)<N!");
+            alglib.ap.assert(alglib.ap.len(y)>=n, "Spline1DConvCubic: Length(Y)<N!");
+            alglib.ap.assert(n2>=2, "Spline1DConvCubic: N2<2!");
+            alglib.ap.assert(alglib.ap.len(x2)>=n2, "Spline1DConvCubic: Length(X2)<N2!");
+            
+            //
+            // check and sort X/Y
+            //
+            ylen = n;
+            if( boundltype==-1 )
+            {
+                ylen = n-1;
+            }
+            alglib.ap.assert(apserv.isfinitevector(x, n), "Spline1DConvCubic: X contains infinite or NAN values!");
+            alglib.ap.assert(apserv.isfinitevector(y, ylen), "Spline1DConvCubic: Y contains infinite or NAN values!");
+            alglib.ap.assert(apserv.isfinitevector(x2, n2), "Spline1DConvCubic: X2 contains infinite or NAN values!");
+            heapsortppoints(ref x, ref y, ref p, n);
+            alglib.ap.assert(apserv.aredistinct(x, n), "Spline1DConvCubic: at least two consequent points are too close!");
+            
+            //
+            // set up DT (we will need it below)
+            //
+            dt = new double[Math.Max(n, n2)];
+            
+            //
+            // sort X2:
+            // * use fake array DT because HeapSortPPoints() needs both integer AND real arrays
+            // * if we have periodic problem, wrap points
+            // * sort them, store permutation at P2
+            //
+            if( boundrtype==-1 && boundltype==-1 )
+            {
+                for(i=0; i<=n2-1; i++)
+                {
+                    t = x2[i];
+                    apserv.apperiodicmap(ref t, x[0], x[n-1], ref t2);
+                    x2[i] = t;
+                }
+            }
+            heapsortppoints(ref x2, ref dt, ref p2, n2);
+            
+            //
+            // Now we've checked and preordered everything, so we:
+            // * call internal GridDiff() function to get Hermite form of spline
+            // * convert using internal Conv() function
+            // * convert Y2 back to original order
+            //
+            spline1dgriddiffcubicinternal(x, ref y, n, boundltype, boundl, boundrtype, boundr, ref d, ref a1, ref a2, ref a3, ref b, ref dt);
+            spline1dconvdiffinternal(x, y, d, n, x2, n2, ref y2, true, ref d1, false, ref d2, false);
+            alglib.ap.assert(alglib.ap.len(dt)>=n2, "Spline1DConvCubic: internal error!");
+            for(i=0; i<=n2-1; i++)
+            {
+                dt[p2[i]] = y2[i];
+            }
+            for(i_=0; i_<=n2-1;i_++)
+            {
+                y2[i_] = dt[i_];
+            }
+        }
+
+
+        /*************************************************************************
+        This function solves following problem: given table y[] of function values
+        at old nodes x[]  and new nodes  x2[],  it calculates and returns table of
+        function values y2[] and derivatives d2[] (calculated at x2[]).
+
+        This function yields same result as Spline1DBuildCubic() call followed  by
+        sequence of Spline1DDiff() calls, but it can be several times faster  when
+        called for ordered X[] and X2[].
+
+        INPUT PARAMETERS:
+            X           -   old spline nodes
+            Y           -   function values
+            X2           -  new spline nodes
+
+        OPTIONAL PARAMETERS:
+            N           -   points count:
+                            * N>=2
+                            * if given, only first N points from X/Y are used
+                            * if not given, automatically detected from X/Y sizes
+                              (len(X) must be equal to len(Y))
+            BoundLType  -   boundary condition type for the left boundary
+            BoundL      -   left boundary condition (first or second derivative,
+                            depending on the BoundLType)
+            BoundRType  -   boundary condition type for the right boundary
+            BoundR      -   right boundary condition (first or second derivative,
+                            depending on the BoundRType)
+            N2          -   new points count:
+                            * N2>=2
+                            * if given, only first N2 points from X2 are used
+                            * if not given, automatically detected from X2 size
+
+        OUTPUT PARAMETERS:
+            F2          -   function values at X2[]
+            D2          -   first derivatives at X2[]
+
+        ORDER OF POINTS
+
+        Subroutine automatically sorts points, so caller  may pass unsorted array.
+        Function  values  are correctly reordered on  return, so F2[I]  is  always
+        equal to S(X2[I]) independently of points order.
+
+        SETTING BOUNDARY VALUES:
+
+        The BoundLType/BoundRType parameters can have the following values:
+            * -1, which corresonds to the periodic (cyclic) boundary conditions.
+                  In this case:
+                  * both BoundLType and BoundRType must be equal to -1.
+                  * BoundL/BoundR are ignored
+                  * Y[last] is ignored (it is assumed to be equal to Y[first]).
+            *  0, which  corresponds  to  the  parabolically   terminated  spline
+                  (BoundL and/or BoundR are ignored).
+            *  1, which corresponds to the first derivative boundary condition
+            *  2, which corresponds to the second derivative boundary condition
+            *  by default, BoundType=0 is used
+
+        PROBLEMS WITH PERIODIC BOUNDARY CONDITIONS:
+
+        Problems with periodic boundary conditions have Y[first_point]=Y[last_point].
+        However, this subroutine doesn't require you to specify equal  values  for
+        the first and last points - it automatically forces them  to  be  equal by
+        copying  Y[first_point]  (corresponds  to the leftmost,  minimal  X[])  to
+        Y[last_point]. However it is recommended to pass consistent values of Y[],
+        i.e. to make Y[first_point]=Y[last_point].
+
+          -- ALGLIB PROJECT --
+             Copyright 03.09.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spline1dconvdiffcubic(double[] x,
+            double[] y,
+            int n,
+            int boundltype,
+            double boundl,
+            int boundrtype,
+            double boundr,
+            double[] x2,
+            int n2,
+            ref double[] y2,
+            ref double[] d2)
+        {
+            double[] a1 = new double[0];
+            double[] a2 = new double[0];
+            double[] a3 = new double[0];
+            double[] b = new double[0];
+            double[] d = new double[0];
+            double[] dt = new double[0];
+            double[] rt1 = new double[0];
+            int[] p = new int[0];
+            int[] p2 = new int[0];
+            int i = 0;
+            int ylen = 0;
+            double t = 0;
+            double t2 = 0;
+            int i_ = 0;
+
+            x = (double[])x.Clone();
+            y = (double[])y.Clone();
+            x2 = (double[])x2.Clone();
+            y2 = new double[0];
+            d2 = new double[0];
+
+            
+            //
+            // check correctness of boundary conditions
+            //
+            alglib.ap.assert(((boundltype==-1 || boundltype==0) || boundltype==1) || boundltype==2, "Spline1DConvDiffCubic: incorrect BoundLType!");
+            alglib.ap.assert(((boundrtype==-1 || boundrtype==0) || boundrtype==1) || boundrtype==2, "Spline1DConvDiffCubic: incorrect BoundRType!");
+            alglib.ap.assert((boundrtype==-1 && boundltype==-1) || (boundrtype!=-1 && boundltype!=-1), "Spline1DConvDiffCubic: incorrect BoundLType/BoundRType!");
+            if( boundltype==1 || boundltype==2 )
+            {
+                alglib.ap.assert(math.isfinite(boundl), "Spline1DConvDiffCubic: BoundL is infinite or NAN!");
+            }
+            if( boundrtype==1 || boundrtype==2 )
+            {
+                alglib.ap.assert(math.isfinite(boundr), "Spline1DConvDiffCubic: BoundR is infinite or NAN!");
+            }
+            
+            //
+            // check lengths of arguments
+            //
+            alglib.ap.assert(n>=2, "Spline1DConvDiffCubic: N<2!");
+            alglib.ap.assert(alglib.ap.len(x)>=n, "Spline1DConvDiffCubic: Length(X)<N!");
+            alglib.ap.assert(alglib.ap.len(y)>=n, "Spline1DConvDiffCubic: Length(Y)<N!");
+            alglib.ap.assert(n2>=2, "Spline1DConvDiffCubic: N2<2!");
+            alglib.ap.assert(alglib.ap.len(x2)>=n2, "Spline1DConvDiffCubic: Length(X2)<N2!");
+            
+            //
+            // check and sort X/Y
+            //
+            ylen = n;
+            if( boundltype==-1 )
+            {
+                ylen = n-1;
+            }
+            alglib.ap.assert(apserv.isfinitevector(x, n), "Spline1DConvDiffCubic: X contains infinite or NAN values!");
+            alglib.ap.assert(apserv.isfinitevector(y, ylen), "Spline1DConvDiffCubic: Y contains infinite or NAN values!");
+            alglib.ap.assert(apserv.isfinitevector(x2, n2), "Spline1DConvDiffCubic: X2 contains infinite or NAN values!");
+            heapsortppoints(ref x, ref y, ref p, n);
+            alglib.ap.assert(apserv.aredistinct(x, n), "Spline1DConvDiffCubic: at least two consequent points are too close!");
+            
+            //
+            // set up DT (we will need it below)
+            //
+            dt = new double[Math.Max(n, n2)];
+            
+            //
+            // sort X2:
+            // * use fake array DT because HeapSortPPoints() needs both integer AND real arrays
+            // * if we have periodic problem, wrap points
+            // * sort them, store permutation at P2
+            //
+            if( boundrtype==-1 && boundltype==-1 )
+            {
+                for(i=0; i<=n2-1; i++)
+                {
+                    t = x2[i];
+                    apserv.apperiodicmap(ref t, x[0], x[n-1], ref t2);
+                    x2[i] = t;
+                }
+            }
+            heapsortppoints(ref x2, ref dt, ref p2, n2);
+            
+            //
+            // Now we've checked and preordered everything, so we:
+            // * call internal GridDiff() function to get Hermite form of spline
+            // * convert using internal Conv() function
+            // * convert Y2 back to original order
+            //
+            spline1dgriddiffcubicinternal(x, ref y, n, boundltype, boundl, boundrtype, boundr, ref d, ref a1, ref a2, ref a3, ref b, ref dt);
+            spline1dconvdiffinternal(x, y, d, n, x2, n2, ref y2, true, ref d2, true, ref rt1, false);
+            alglib.ap.assert(alglib.ap.len(dt)>=n2, "Spline1DConvDiffCubic: internal error!");
+            for(i=0; i<=n2-1; i++)
+            {
+                dt[p2[i]] = y2[i];
+            }
+            for(i_=0; i_<=n2-1;i_++)
+            {
+                y2[i_] = dt[i_];
+            }
+            for(i=0; i<=n2-1; i++)
+            {
+                dt[p2[i]] = d2[i];
+            }
+            for(i_=0; i_<=n2-1;i_++)
+            {
+                d2[i_] = dt[i_];
+            }
+        }
+
+
+        /*************************************************************************
+        This function solves following problem: given table y[] of function values
+        at old nodes x[]  and new nodes  x2[],  it calculates and returns table of
+        function  values  y2[],  first  and  second  derivatives  d2[]  and  dd2[]
+        (calculated at x2[]).
+
+        This function yields same result as Spline1DBuildCubic() call followed  by
+        sequence of Spline1DDiff() calls, but it can be several times faster  when
+        called for ordered X[] and X2[].
+
+        INPUT PARAMETERS:
+            X           -   old spline nodes
+            Y           -   function values
+            X2           -  new spline nodes
+
+        OPTIONAL PARAMETERS:
+            N           -   points count:
+                            * N>=2
+                            * if given, only first N points from X/Y are used
+                            * if not given, automatically detected from X/Y sizes
+                              (len(X) must be equal to len(Y))
+            BoundLType  -   boundary condition type for the left boundary
+            BoundL      -   left boundary condition (first or second derivative,
+                            depending on the BoundLType)
+            BoundRType  -   boundary condition type for the right boundary
+            BoundR      -   right boundary condition (first or second derivative,
+                            depending on the BoundRType)
+            N2          -   new points count:
+                            * N2>=2
+                            * if given, only first N2 points from X2 are used
+                            * if not given, automatically detected from X2 size
+
+        OUTPUT PARAMETERS:
+            F2          -   function values at X2[]
+            D2          -   first derivatives at X2[]
+            DD2         -   second derivatives at X2[]
+
+        ORDER OF POINTS
+
+        Subroutine automatically sorts points, so caller  may pass unsorted array.
+        Function  values  are correctly reordered on  return, so F2[I]  is  always
+        equal to S(X2[I]) independently of points order.
+
+        SETTING BOUNDARY VALUES:
+
+        The BoundLType/BoundRType parameters can have the following values:
+            * -1, which corresonds to the periodic (cyclic) boundary conditions.
+                  In this case:
+                  * both BoundLType and BoundRType must be equal to -1.
+                  * BoundL/BoundR are ignored
+                  * Y[last] is ignored (it is assumed to be equal to Y[first]).
+            *  0, which  corresponds  to  the  parabolically   terminated  spline
+                  (BoundL and/or BoundR are ignored).
+            *  1, which corresponds to the first derivative boundary condition
+            *  2, which corresponds to the second derivative boundary condition
+            *  by default, BoundType=0 is used
+
+        PROBLEMS WITH PERIODIC BOUNDARY CONDITIONS:
+
+        Problems with periodic boundary conditions have Y[first_point]=Y[last_point].
+        However, this subroutine doesn't require you to specify equal  values  for
+        the first and last points - it automatically forces them  to  be  equal by
+        copying  Y[first_point]  (corresponds  to the leftmost,  minimal  X[])  to
+        Y[last_point]. However it is recommended to pass consistent values of Y[],
+        i.e. to make Y[first_point]=Y[last_point].
+
+          -- ALGLIB PROJECT --
+             Copyright 03.09.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spline1dconvdiff2cubic(double[] x,
+            double[] y,
+            int n,
+            int boundltype,
+            double boundl,
+            int boundrtype,
+            double boundr,
+            double[] x2,
+            int n2,
+            ref double[] y2,
+            ref double[] d2,
+            ref double[] dd2)
+        {
+            double[] a1 = new double[0];
+            double[] a2 = new double[0];
+            double[] a3 = new double[0];
+            double[] b = new double[0];
+            double[] d = new double[0];
+            double[] dt = new double[0];
+            int[] p = new int[0];
+            int[] p2 = new int[0];
+            int i = 0;
+            int ylen = 0;
+            double t = 0;
+            double t2 = 0;
+            int i_ = 0;
+
+            x = (double[])x.Clone();
+            y = (double[])y.Clone();
+            x2 = (double[])x2.Clone();
+            y2 = new double[0];
+            d2 = new double[0];
+            dd2 = new double[0];
+
+            
+            //
+            // check correctness of boundary conditions
+            //
+            alglib.ap.assert(((boundltype==-1 || boundltype==0) || boundltype==1) || boundltype==2, "Spline1DConvDiff2Cubic: incorrect BoundLType!");
+            alglib.ap.assert(((boundrtype==-1 || boundrtype==0) || boundrtype==1) || boundrtype==2, "Spline1DConvDiff2Cubic: incorrect BoundRType!");
+            alglib.ap.assert((boundrtype==-1 && boundltype==-1) || (boundrtype!=-1 && boundltype!=-1), "Spline1DConvDiff2Cubic: incorrect BoundLType/BoundRType!");
+            if( boundltype==1 || boundltype==2 )
+            {
+                alglib.ap.assert(math.isfinite(boundl), "Spline1DConvDiff2Cubic: BoundL is infinite or NAN!");
+            }
+            if( boundrtype==1 || boundrtype==2 )
+            {
+                alglib.ap.assert(math.isfinite(boundr), "Spline1DConvDiff2Cubic: BoundR is infinite or NAN!");
+            }
+            
+            //
+            // check lengths of arguments
+            //
+            alglib.ap.assert(n>=2, "Spline1DConvDiff2Cubic: N<2!");
+            alglib.ap.assert(alglib.ap.len(x)>=n, "Spline1DConvDiff2Cubic: Length(X)<N!");
+            alglib.ap.assert(alglib.ap.len(y)>=n, "Spline1DConvDiff2Cubic: Length(Y)<N!");
+            alglib.ap.assert(n2>=2, "Spline1DConvDiff2Cubic: N2<2!");
+            alglib.ap.assert(alglib.ap.len(x2)>=n2, "Spline1DConvDiff2Cubic: Length(X2)<N2!");
+            
+            //
+            // check and sort X/Y
+            //
+            ylen = n;
+            if( boundltype==-1 )
+            {
+                ylen = n-1;
+            }
+            alglib.ap.assert(apserv.isfinitevector(x, n), "Spline1DConvDiff2Cubic: X contains infinite or NAN values!");
+            alglib.ap.assert(apserv.isfinitevector(y, ylen), "Spline1DConvDiff2Cubic: Y contains infinite or NAN values!");
+            alglib.ap.assert(apserv.isfinitevector(x2, n2), "Spline1DConvDiff2Cubic: X2 contains infinite or NAN values!");
+            heapsortppoints(ref x, ref y, ref p, n);
+            alglib.ap.assert(apserv.aredistinct(x, n), "Spline1DConvDiff2Cubic: at least two consequent points are too close!");
+            
+            //
+            // set up DT (we will need it below)
+            //
+            dt = new double[Math.Max(n, n2)];
+            
+            //
+            // sort X2:
+            // * use fake array DT because HeapSortPPoints() needs both integer AND real arrays
+            // * if we have periodic problem, wrap points
+            // * sort them, store permutation at P2
+            //
+            if( boundrtype==-1 && boundltype==-1 )
+            {
+                for(i=0; i<=n2-1; i++)
+                {
+                    t = x2[i];
+                    apserv.apperiodicmap(ref t, x[0], x[n-1], ref t2);
+                    x2[i] = t;
+                }
+            }
+            heapsortppoints(ref x2, ref dt, ref p2, n2);
+            
+            //
+            // Now we've checked and preordered everything, so we:
+            // * call internal GridDiff() function to get Hermite form of spline
+            // * convert using internal Conv() function
+            // * convert Y2 back to original order
+            //
+            spline1dgriddiffcubicinternal(x, ref y, n, boundltype, boundl, boundrtype, boundr, ref d, ref a1, ref a2, ref a3, ref b, ref dt);
+            spline1dconvdiffinternal(x, y, d, n, x2, n2, ref y2, true, ref d2, true, ref dd2, true);
+            alglib.ap.assert(alglib.ap.len(dt)>=n2, "Spline1DConvDiff2Cubic: internal error!");
+            for(i=0; i<=n2-1; i++)
+            {
+                dt[p2[i]] = y2[i];
+            }
+            for(i_=0; i_<=n2-1;i_++)
+            {
+                y2[i_] = dt[i_];
+            }
+            for(i=0; i<=n2-1; i++)
+            {
+                dt[p2[i]] = d2[i];
+            }
+            for(i_=0; i_<=n2-1;i_++)
+            {
+                d2[i_] = dt[i_];
+            }
+            for(i=0; i<=n2-1; i++)
+            {
+                dt[p2[i]] = dd2[i];
+            }
+            for(i_=0; i_<=n2-1;i_++)
+            {
+                dd2[i_] = dt[i_];
+            }
+        }
+
+
+        /*************************************************************************
+        This subroutine builds Catmull-Rom spline interpolant.
+
+        INPUT PARAMETERS:
+            X           -   spline nodes, array[0..N-1].
+            Y           -   function values, array[0..N-1].
+            
+        OPTIONAL PARAMETERS:
+            N           -   points count:
+                            * N>=2
+                            * if given, only first N points are used to build spline
+                            * if not given, automatically detected from X/Y sizes
+                              (len(X) must be equal to len(Y))
+            BoundType   -   boundary condition type:
+                            * -1 for periodic boundary condition
+                            *  0 for parabolically terminated spline (default)
+            Tension     -   tension parameter:
+                            * tension=0   corresponds to classic Catmull-Rom spline (default)
+                            * 0<tension<1 corresponds to more general form - cardinal spline
+
+        OUTPUT PARAMETERS:
+            C           -   spline interpolant
+
+
+        ORDER OF POINTS
+
+        Subroutine automatically sorts points, so caller may pass unsorted array.
+
+        PROBLEMS WITH PERIODIC BOUNDARY CONDITIONS:
+
+        Problems with periodic boundary conditions have Y[first_point]=Y[last_point].
+        However, this subroutine doesn't require you to specify equal  values  for
+        the first and last points - it automatically forces them  to  be  equal by
+        copying  Y[first_point]  (corresponds  to the leftmost,  minimal  X[])  to
+        Y[last_point]. However it is recommended to pass consistent values of Y[],
+        i.e. to make Y[first_point]=Y[last_point].
+
+          -- ALGLIB PROJECT --
+             Copyright 23.06.2007 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spline1dbuildcatmullrom(double[] x,
+            double[] y,
+            int n,
+            int boundtype,
+            double tension,
+            spline1dinterpolant c)
+        {
+            double[] d = new double[0];
+            int i = 0;
+
+            x = (double[])x.Clone();
+            y = (double[])y.Clone();
+
+            alglib.ap.assert(n>=2, "Spline1DBuildCatmullRom: N<2!");
+            alglib.ap.assert(boundtype==-1 || boundtype==0, "Spline1DBuildCatmullRom: incorrect BoundType!");
+            alglib.ap.assert((double)(tension)>=(double)(0), "Spline1DBuildCatmullRom: Tension<0!");
+            alglib.ap.assert((double)(tension)<=(double)(1), "Spline1DBuildCatmullRom: Tension>1!");
+            alglib.ap.assert(alglib.ap.len(x)>=n, "Spline1DBuildCatmullRom: Length(X)<N!");
+            alglib.ap.assert(alglib.ap.len(y)>=n, "Spline1DBuildCatmullRom: Length(Y)<N!");
+            
+            //
+            // check and sort points
+            //
+            alglib.ap.assert(apserv.isfinitevector(x, n), "Spline1DBuildCatmullRom: X contains infinite or NAN values!");
+            alglib.ap.assert(apserv.isfinitevector(y, n), "Spline1DBuildCatmullRom: Y contains infinite or NAN values!");
+            heapsortpoints(ref x, ref y, n);
+            alglib.ap.assert(apserv.aredistinct(x, n), "Spline1DBuildCatmullRom: at least two consequent points are too close!");
+            
+            //
+            // Special cases:
+            // * N=2, parabolic terminated boundary condition on both ends
+            // * N=2, periodic boundary condition
+            //
+            if( n==2 && boundtype==0 )
+            {
+                
+                //
+                // Just linear spline
+                //
+                spline1dbuildlinear(x, y, n, c);
+                return;
+            }
+            if( n==2 && boundtype==-1 )
+            {
+                
+                //
+                // Same as cubic spline with periodic conditions
+                //
+                spline1dbuildcubic(x, y, n, -1, 0.0, -1, 0.0, c);
+                return;
+            }
+            
+            //
+            // Periodic or non-periodic boundary conditions
+            //
+            if( boundtype==-1 )
+            {
+                
+                //
+                // Periodic boundary conditions
+                //
+                y[n-1] = y[0];
+                d = new double[n];
+                d[0] = (y[1]-y[n-2])/(2*(x[1]-x[0]+x[n-1]-x[n-2]));
+                for(i=1; i<=n-2; i++)
+                {
+                    d[i] = (1-tension)*(y[i+1]-y[i-1])/(x[i+1]-x[i-1]);
+                }
+                d[n-1] = d[0];
+                
+                //
+                // Now problem is reduced to the cubic Hermite spline
+                //
+                spline1dbuildhermite(x, y, d, n, c);
+                c.periodic = true;
+            }
+            else
+            {
+                
+                //
+                // Non-periodic boundary conditions
+                //
+                d = new double[n];
+                for(i=1; i<=n-2; i++)
+                {
+                    d[i] = (1-tension)*(y[i+1]-y[i-1])/(x[i+1]-x[i-1]);
+                }
+                d[0] = 2*(y[1]-y[0])/(x[1]-x[0])-d[1];
+                d[n-1] = 2*(y[n-1]-y[n-2])/(x[n-1]-x[n-2])-d[n-2];
+                
+                //
+                // Now problem is reduced to the cubic Hermite spline
+                //
+                spline1dbuildhermite(x, y, d, n, c);
+            }
+        }
+
+
+        /*************************************************************************
+        This subroutine builds Hermite spline interpolant.
+
+        INPUT PARAMETERS:
+            X           -   spline nodes, array[0..N-1]
+            Y           -   function values, array[0..N-1]
+            D           -   derivatives, array[0..N-1]
+            N           -   points count (optional):
+                            * N>=2
+                            * if given, only first N points are used to build spline
+                            * if not given, automatically detected from X/Y sizes
+                              (len(X) must be equal to len(Y))
+
+        OUTPUT PARAMETERS:
+            C           -   spline interpolant.
+
+
+        ORDER OF POINTS
+
+        Subroutine automatically sorts points, so caller may pass unsorted array.
+
+          -- ALGLIB PROJECT --
+             Copyright 23.06.2007 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spline1dbuildhermite(double[] x,
+            double[] y,
+            double[] d,
+            int n,
+            spline1dinterpolant c)
+        {
+            int i = 0;
+            double delta = 0;
+            double delta2 = 0;
+            double delta3 = 0;
+
+            x = (double[])x.Clone();
+            y = (double[])y.Clone();
+            d = (double[])d.Clone();
+
+            alglib.ap.assert(n>=2, "Spline1DBuildHermite: N<2!");
+            alglib.ap.assert(alglib.ap.len(x)>=n, "Spline1DBuildHermite: Length(X)<N!");
+            alglib.ap.assert(alglib.ap.len(y)>=n, "Spline1DBuildHermite: Length(Y)<N!");
+            alglib.ap.assert(alglib.ap.len(d)>=n, "Spline1DBuildHermite: Length(D)<N!");
+            
+            //
+            // check and sort points
+            //
+            alglib.ap.assert(apserv.isfinitevector(x, n), "Spline1DBuildHermite: X contains infinite or NAN values!");
+            alglib.ap.assert(apserv.isfinitevector(y, n), "Spline1DBuildHermite: Y contains infinite or NAN values!");
+            alglib.ap.assert(apserv.isfinitevector(d, n), "Spline1DBuildHermite: D contains infinite or NAN values!");
+            heapsortdpoints(ref x, ref y, ref d, n);
+            alglib.ap.assert(apserv.aredistinct(x, n), "Spline1DBuildHermite: at least two consequent points are too close!");
+            
+            //
+            // Build
+            //
+            c.x = new double[n];
+            c.c = new double[4*(n-1)+2];
+            c.periodic = false;
+            c.k = 3;
+            c.n = n;
+            c.continuity = 1;
+            for(i=0; i<=n-1; i++)
+            {
+                c.x[i] = x[i];
+            }
+            for(i=0; i<=n-2; i++)
+            {
+                delta = x[i+1]-x[i];
+                delta2 = math.sqr(delta);
+                delta3 = delta*delta2;
+                c.c[4*i+0] = y[i];
+                c.c[4*i+1] = d[i];
+                c.c[4*i+2] = (3*(y[i+1]-y[i])-2*d[i]*delta-d[i+1]*delta)/delta2;
+                c.c[4*i+3] = (2*(y[i]-y[i+1])+d[i]*delta+d[i+1]*delta)/delta3;
+            }
+            c.c[4*(n-1)+0] = y[n-1];
+            c.c[4*(n-1)+1] = d[n-1];
+        }
+
+
+        /*************************************************************************
+        This subroutine builds Akima spline interpolant
+
+        INPUT PARAMETERS:
+            X           -   spline nodes, array[0..N-1]
+            Y           -   function values, array[0..N-1]
+            N           -   points count (optional):
+                            * N>=5
+                            * if given, only first N points are used to build spline
+                            * if not given, automatically detected from X/Y sizes
+                              (len(X) must be equal to len(Y))
+
+        OUTPUT PARAMETERS:
+            C           -   spline interpolant
+
+
+        ORDER OF POINTS
+
+        Subroutine automatically sorts points, so caller may pass unsorted array.
+
+          -- ALGLIB PROJECT --
+             Copyright 24.06.2007 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spline1dbuildakima(double[] x,
+            double[] y,
+            int n,
+            spline1dinterpolant c)
+        {
+            int i = 0;
+            double[] d = new double[0];
+            double[] w = new double[0];
+            double[] diff = new double[0];
+
+            x = (double[])x.Clone();
+            y = (double[])y.Clone();
+
+            alglib.ap.assert(n>=5, "Spline1DBuildAkima: N<5!");
+            alglib.ap.assert(alglib.ap.len(x)>=n, "Spline1DBuildAkima: Length(X)<N!");
+            alglib.ap.assert(alglib.ap.len(y)>=n, "Spline1DBuildAkima: Length(Y)<N!");
+            
+            //
+            // check and sort points
+            //
+            alglib.ap.assert(apserv.isfinitevector(x, n), "Spline1DBuildAkima: X contains infinite or NAN values!");
+            alglib.ap.assert(apserv.isfinitevector(y, n), "Spline1DBuildAkima: Y contains infinite or NAN values!");
+            heapsortpoints(ref x, ref y, n);
+            alglib.ap.assert(apserv.aredistinct(x, n), "Spline1DBuildAkima: at least two consequent points are too close!");
+            
+            //
+            // Prepare W (weights), Diff (divided differences)
+            //
+            w = new double[n-1];
+            diff = new double[n-1];
+            for(i=0; i<=n-2; i++)
+            {
+                diff[i] = (y[i+1]-y[i])/(x[i+1]-x[i]);
+            }
+            for(i=1; i<=n-2; i++)
+            {
+                w[i] = Math.Abs(diff[i]-diff[i-1]);
+            }
+            
+            //
+            // Prepare Hermite interpolation scheme
+            //
+            d = new double[n];
+            for(i=2; i<=n-3; i++)
+            {
+                if( (double)(Math.Abs(w[i-1])+Math.Abs(w[i+1]))!=(double)(0) )
+                {
+                    d[i] = (w[i+1]*diff[i-1]+w[i-1]*diff[i])/(w[i+1]+w[i-1]);
+                }
+                else
+                {
+                    d[i] = ((x[i+1]-x[i])*diff[i-1]+(x[i]-x[i-1])*diff[i])/(x[i+1]-x[i-1]);
+                }
+            }
+            d[0] = diffthreepoint(x[0], x[0], y[0], x[1], y[1], x[2], y[2]);
+            d[1] = diffthreepoint(x[1], x[0], y[0], x[1], y[1], x[2], y[2]);
+            d[n-2] = diffthreepoint(x[n-2], x[n-3], y[n-3], x[n-2], y[n-2], x[n-1], y[n-1]);
+            d[n-1] = diffthreepoint(x[n-1], x[n-3], y[n-3], x[n-2], y[n-2], x[n-1], y[n-1]);
+            
+            //
+            // Build Akima spline using Hermite interpolation scheme
+            //
+            spline1dbuildhermite(x, y, d, n, c);
+        }
+
+
+        /*************************************************************************
+        This subroutine calculates the value of the spline at the given point X.
+
+        INPUT PARAMETERS:
+            C   -   spline interpolant
+            X   -   point
+
+        Result:
+            S(x)
+
+          -- ALGLIB PROJECT --
+             Copyright 23.06.2007 by Bochkanov Sergey
+        *************************************************************************/
+        public static double spline1dcalc(spline1dinterpolant c,
+            double x)
+        {
+            double result = 0;
+            int l = 0;
+            int r = 0;
+            int m = 0;
+            double t = 0;
+
+            alglib.ap.assert(c.k==3, "Spline1DCalc: internal error");
+            alglib.ap.assert(!Double.IsInfinity(x), "Spline1DCalc: infinite X!");
+            
+            //
+            // special case: NaN
+            //
+            if( Double.IsNaN(x) )
+            {
+                result = Double.NaN;
+                return result;
+            }
+            
+            //
+            // correct if periodic
+            //
+            if( c.periodic )
+            {
+                apserv.apperiodicmap(ref x, c.x[0], c.x[c.n-1], ref t);
+            }
+            
+            //
+            // Binary search in the [ x[0], ..., x[n-2] ] (x[n-1] is not included)
+            //
+            l = 0;
+            r = c.n-2+1;
+            while( l!=r-1 )
+            {
+                m = (l+r)/2;
+                if( c.x[m]>=x )
+                {
+                    r = m;
+                }
+                else
+                {
+                    l = m;
+                }
+            }
+            
+            //
+            // Interpolation
+            //
+            x = x-c.x[l];
+            m = 4*l;
+            result = c.c[m]+x*(c.c[m+1]+x*(c.c[m+2]+x*c.c[m+3]));
+            return result;
+        }
+
+
+        /*************************************************************************
+        This subroutine differentiates the spline.
+
+        INPUT PARAMETERS:
+            C   -   spline interpolant.
+            X   -   point
+
+        Result:
+            S   -   S(x)
+            DS  -   S'(x)
+            D2S -   S''(x)
+
+          -- ALGLIB PROJECT --
+             Copyright 24.06.2007 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spline1ddiff(spline1dinterpolant c,
+            double x,
+            ref double s,
+            ref double ds,
+            ref double d2s)
+        {
+            int l = 0;
+            int r = 0;
+            int m = 0;
+            double t = 0;
+
+            s = 0;
+            ds = 0;
+            d2s = 0;
+
+            alglib.ap.assert(c.k==3, "Spline1DDiff: internal error");
+            alglib.ap.assert(!Double.IsInfinity(x), "Spline1DDiff: infinite X!");
+            
+            //
+            // special case: NaN
+            //
+            if( Double.IsNaN(x) )
+            {
+                s = Double.NaN;
+                ds = Double.NaN;
+                d2s = Double.NaN;
+                return;
+            }
+            
+            //
+            // correct if periodic
+            //
+            if( c.periodic )
+            {
+                apserv.apperiodicmap(ref x, c.x[0], c.x[c.n-1], ref t);
+            }
+            
+            //
+            // Binary search
+            //
+            l = 0;
+            r = c.n-2+1;
+            while( l!=r-1 )
+            {
+                m = (l+r)/2;
+                if( c.x[m]>=x )
+                {
+                    r = m;
+                }
+                else
+                {
+                    l = m;
+                }
+            }
+            
+            //
+            // Differentiation
+            //
+            x = x-c.x[l];
+            m = 4*l;
+            s = c.c[m]+x*(c.c[m+1]+x*(c.c[m+2]+x*c.c[m+3]));
+            ds = c.c[m+1]+2*x*c.c[m+2]+3*math.sqr(x)*c.c[m+3];
+            d2s = 2*c.c[m+2]+6*x*c.c[m+3];
+        }
+
+
+        /*************************************************************************
+        This subroutine makes the copy of the spline.
+
+        INPUT PARAMETERS:
+            C   -   spline interpolant.
+
+        Result:
+            CC  -   spline copy
+
+          -- ALGLIB PROJECT --
+             Copyright 29.06.2007 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spline1dcopy(spline1dinterpolant c,
+            spline1dinterpolant cc)
+        {
+            int s = 0;
+            int i_ = 0;
+
+            cc.periodic = c.periodic;
+            cc.n = c.n;
+            cc.k = c.k;
+            cc.continuity = c.continuity;
+            cc.x = new double[cc.n];
+            for(i_=0; i_<=cc.n-1;i_++)
+            {
+                cc.x[i_] = c.x[i_];
+            }
+            s = alglib.ap.len(c.c);
+            cc.c = new double[s];
+            for(i_=0; i_<=s-1;i_++)
+            {
+                cc.c[i_] = c.c[i_];
+            }
+        }
+
+
+        /*************************************************************************
+        This subroutine unpacks the spline into the coefficients table.
+
+        INPUT PARAMETERS:
+            C   -   spline interpolant.
+            X   -   point
+
+        Result:
+            Tbl -   coefficients table, unpacked format, array[0..N-2, 0..5].
+                    For I = 0...N-2:
+                        Tbl[I,0] = X[i]
+                        Tbl[I,1] = X[i+1]
+                        Tbl[I,2] = C0
+                        Tbl[I,3] = C1
+                        Tbl[I,4] = C2
+                        Tbl[I,5] = C3
+                    On [x[i], x[i+1]] spline is equals to:
+                        S(x) = C0 + C1*t + C2*t^2 + C3*t^3
+                        t = x-x[i]
+
+          -- ALGLIB PROJECT --
+             Copyright 29.06.2007 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spline1dunpack(spline1dinterpolant c,
+            ref int n,
+            ref double[,] tbl)
+        {
+            int i = 0;
+            int j = 0;
+
+            n = 0;
+            tbl = new double[0,0];
+
+            tbl = new double[c.n-2+1, 2+c.k+1];
+            n = c.n;
+            
+            //
+            // Fill
+            //
+            for(i=0; i<=n-2; i++)
+            {
+                tbl[i,0] = c.x[i];
+                tbl[i,1] = c.x[i+1];
+                for(j=0; j<=c.k; j++)
+                {
+                    tbl[i,2+j] = c.c[(c.k+1)*i+j];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This subroutine performs linear transformation of the spline argument.
+
+        INPUT PARAMETERS:
+            C   -   spline interpolant.
+            A, B-   transformation coefficients: x = A*t + B
+        Result:
+            C   -   transformed spline
+
+          -- ALGLIB PROJECT --
+             Copyright 30.06.2007 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spline1dlintransx(spline1dinterpolant c,
+            double a,
+            double b)
+        {
+            int i = 0;
+            int n = 0;
+            double v = 0;
+            double dv = 0;
+            double d2v = 0;
+            double[] x = new double[0];
+            double[] y = new double[0];
+            double[] d = new double[0];
+            bool isperiodic = new bool();
+            int contval = 0;
+
+            alglib.ap.assert(c.k==3, "Spline1DLinTransX: internal error");
+            n = c.n;
+            x = new double[n];
+            y = new double[n];
+            d = new double[n];
+            
+            //
+            // Unpack, X, Y, dY/dX.
+            // Scale and pack with Spline1DBuildHermite again.
+            //
+            if( (double)(a)==(double)(0) )
+            {
+                
+                //
+                // Special case: A=0
+                //
+                v = spline1dcalc(c, b);
+                for(i=0; i<=n-1; i++)
+                {
+                    x[i] = c.x[i];
+                    y[i] = v;
+                    d[i] = 0.0;
+                }
+            }
+            else
+            {
+                
+                //
+                // General case, A<>0
+                //
+                for(i=0; i<=n-1; i++)
+                {
+                    x[i] = c.x[i];
+                    spline1ddiff(c, x[i], ref v, ref dv, ref d2v);
+                    x[i] = (x[i]-b)/a;
+                    y[i] = v;
+                    d[i] = a*dv;
+                }
+            }
+            isperiodic = c.periodic;
+            contval = c.continuity;
+            if( contval>0 )
+            {
+                spline1dbuildhermite(x, y, d, n, c);
+            }
+            else
+            {
+                spline1dbuildlinear(x, y, n, c);
+            }
+            c.periodic = isperiodic;
+            c.continuity = contval;
+        }
+
+
+        /*************************************************************************
+        This subroutine performs linear transformation of the spline.
+
+        INPUT PARAMETERS:
+            C   -   spline interpolant.
+            A, B-   transformation coefficients: S2(x) = A*S(x) + B
+        Result:
+            C   -   transformed spline
+
+          -- ALGLIB PROJECT --
+             Copyright 30.06.2007 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spline1dlintransy(spline1dinterpolant c,
+            double a,
+            double b)
+        {
+            int i = 0;
+            int j = 0;
+            int n = 0;
+
+            alglib.ap.assert(c.k==3, "Spline1DLinTransX: internal error");
+            n = c.n;
+            for(i=0; i<=n-2; i++)
+            {
+                c.c[4*i] = a*c.c[4*i]+b;
+                for(j=1; j<=3; j++)
+                {
+                    c.c[4*i+j] = a*c.c[4*i+j];
+                }
+            }
+            c.c[4*(n-1)+0] = a*c.c[4*(n-1)+0]+b;
+            c.c[4*(n-1)+1] = a*c.c[4*(n-1)+1];
+        }
+
+
+        /*************************************************************************
+        This subroutine integrates the spline.
+
+        INPUT PARAMETERS:
+            C   -   spline interpolant.
+            X   -   right bound of the integration interval [a, x],
+                    here 'a' denotes min(x[])
+        Result:
+            integral(S(t)dt,a,x)
+
+          -- ALGLIB PROJECT --
+             Copyright 23.06.2007 by Bochkanov Sergey
+        *************************************************************************/
+        public static double spline1dintegrate(spline1dinterpolant c,
+            double x)
+        {
+            double result = 0;
+            int n = 0;
+            int i = 0;
+            int j = 0;
+            int l = 0;
+            int r = 0;
+            int m = 0;
+            double w = 0;
+            double v = 0;
+            double t = 0;
+            double intab = 0;
+            double additionalterm = 0;
+
+            n = c.n;
+            
+            //
+            // Periodic splines require special treatment. We make
+            // following transformation:
+            //
+            //     integral(S(t)dt,A,X) = integral(S(t)dt,A,Z)+AdditionalTerm
+            //
+            // here X may lie outside of [A,B], Z lies strictly in [A,B],
+            // AdditionalTerm is equals to integral(S(t)dt,A,B) times some
+            // integer number (may be zero).
+            //
+            if( c.periodic && ((double)(x)<(double)(c.x[0]) || (double)(x)>(double)(c.x[c.n-1])) )
+            {
+                
+                //
+                // compute integral(S(x)dx,A,B)
+                //
+                intab = 0;
+                for(i=0; i<=c.n-2; i++)
+                {
+                    w = c.x[i+1]-c.x[i];
+                    m = (c.k+1)*i;
+                    intab = intab+c.c[m]*w;
+                    v = w;
+                    for(j=1; j<=c.k; j++)
+                    {
+                        v = v*w;
+                        intab = intab+c.c[m+j]*v/(j+1);
+                    }
+                }
+                
+                //
+                // map X into [A,B]
+                //
+                apserv.apperiodicmap(ref x, c.x[0], c.x[c.n-1], ref t);
+                additionalterm = t*intab;
+            }
+            else
+            {
+                additionalterm = 0;
+            }
+            
+            //
+            // Binary search in the [ x[0], ..., x[n-2] ] (x[n-1] is not included)
+            //
+            l = 0;
+            r = n-2+1;
+            while( l!=r-1 )
+            {
+                m = (l+r)/2;
+                if( (double)(c.x[m])>=(double)(x) )
+                {
+                    r = m;
+                }
+                else
+                {
+                    l = m;
+                }
+            }
+            
+            //
+            // Integration
+            //
+            result = 0;
+            for(i=0; i<=l-1; i++)
+            {
+                w = c.x[i+1]-c.x[i];
+                m = (c.k+1)*i;
+                result = result+c.c[m]*w;
+                v = w;
+                for(j=1; j<=c.k; j++)
+                {
+                    v = v*w;
+                    result = result+c.c[m+j]*v/(j+1);
+                }
+            }
+            w = x-c.x[l];
+            m = (c.k+1)*l;
+            v = w;
+            result = result+c.c[m]*w;
+            for(j=1; j<=c.k; j++)
+            {
+                v = v*w;
+                result = result+c.c[m+j]*v/(j+1);
+            }
+            result = result+additionalterm;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Internal version of Spline1DConvDiff
+
+        Converts from Hermite spline given by grid XOld to new grid X2
+
+        INPUT PARAMETERS:
+            XOld    -   old grid
+            YOld    -   values at old grid
+            DOld    -   first derivative at old grid
+            N       -   grid size
+            X2      -   new grid
+            N2      -   new grid size
+            Y       -   possibly preallocated output array
+                        (reallocate if too small)
+            NeedY   -   do we need Y?
+            D1      -   possibly preallocated output array
+                        (reallocate if too small)
+            NeedD1  -   do we need D1?
+            D2      -   possibly preallocated output array
+                        (reallocate if too small)
+            NeedD2  -   do we need D1?
+
+        OUTPUT ARRAYS:
+            Y       -   values, if needed
+            D1      -   first derivative, if needed
+            D2      -   second derivative, if needed
+
+          -- ALGLIB PROJECT --
+             Copyright 03.09.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spline1dconvdiffinternal(double[] xold,
+            double[] yold,
+            double[] dold,
+            int n,
+            double[] x2,
+            int n2,
+            ref double[] y,
+            bool needy,
+            ref double[] d1,
+            bool needd1,
+            ref double[] d2,
+            bool needd2)
+        {
+            int intervalindex = 0;
+            int pointindex = 0;
+            bool havetoadvance = new bool();
+            double c0 = 0;
+            double c1 = 0;
+            double c2 = 0;
+            double c3 = 0;
+            double a = 0;
+            double b = 0;
+            double w = 0;
+            double w2 = 0;
+            double w3 = 0;
+            double fa = 0;
+            double fb = 0;
+            double da = 0;
+            double db = 0;
+            double t = 0;
+
+            
+            //
+            // Prepare space
+            //
+            if( needy && alglib.ap.len(y)<n2 )
+            {
+                y = new double[n2];
+            }
+            if( needd1 && alglib.ap.len(d1)<n2 )
+            {
+                d1 = new double[n2];
+            }
+            if( needd2 && alglib.ap.len(d2)<n2 )
+            {
+                d2 = new double[n2];
+            }
+            
+            //
+            // These assignments aren't actually needed
+            // (variables are initialized in the loop below),
+            // but without them compiler will complain about uninitialized locals
+            //
+            c0 = 0;
+            c1 = 0;
+            c2 = 0;
+            c3 = 0;
+            a = 0;
+            b = 0;
+            
+            //
+            // Cycle
+            //
+            intervalindex = -1;
+            pointindex = 0;
+            while( true )
+            {
+                
+                //
+                // are we ready to exit?
+                //
+                if( pointindex>=n2 )
+                {
+                    break;
+                }
+                t = x2[pointindex];
+                
+                //
+                // do we need to advance interval?
+                //
+                havetoadvance = false;
+                if( intervalindex==-1 )
+                {
+                    havetoadvance = true;
+                }
+                else
+                {
+                    if( intervalindex<n-2 )
+                    {
+                        havetoadvance = (double)(t)>=(double)(b);
+                    }
+                }
+                if( havetoadvance )
+                {
+                    intervalindex = intervalindex+1;
+                    a = xold[intervalindex];
+                    b = xold[intervalindex+1];
+                    w = b-a;
+                    w2 = w*w;
+                    w3 = w*w2;
+                    fa = yold[intervalindex];
+                    fb = yold[intervalindex+1];
+                    da = dold[intervalindex];
+                    db = dold[intervalindex+1];
+                    c0 = fa;
+                    c1 = da;
+                    c2 = (3*(fb-fa)-2*da*w-db*w)/w2;
+                    c3 = (2*(fa-fb)+da*w+db*w)/w3;
+                    continue;
+                }
+                
+                //
+                // Calculate spline and its derivatives using power basis
+                //
+                t = t-a;
+                if( needy )
+                {
+                    y[pointindex] = c0+t*(c1+t*(c2+t*c3));
+                }
+                if( needd1 )
+                {
+                    d1[pointindex] = c1+2*t*c2+3*t*t*c3;
+                }
+                if( needd2 )
+                {
+                    d2[pointindex] = 2*c2+6*t*c3;
+                }
+                pointindex = pointindex+1;
+            }
+        }
+
+
+        /*************************************************************************
+        This function finds all roots and extrema of the spline S(x)  defined  at
+        [A,B] (interval which contains spline nodes).
+
+        It  does not extrapolates function, so roots and extrema located  outside 
+        of [A,B] will not be found. It returns all isolated (including  multiple)
+        roots and extrema.
+
+        INPUT PARAMETERS
+            C           -   spline interpolant
+            
+        OUTPUT PARAMETERS
+            R           -   array[NR], contains roots of the spline. 
+                            In case there is no roots, this array has zero length.
+            NR          -   number of roots, >=0
+            DR          -   is set to True in case there is at least one interval
+                            where spline is just a zero constant. Such degenerate
+                            cases are not reported in the R/NR
+            E           -   array[NE], contains  extrema  (maximums/minimums)  of 
+                            the spline. In case there is no extrema,  this  array 
+                            has zero length.
+            ET          -   array[NE], extrema types:
+                            * ET[i]>0 in case I-th extrema is a minimum
+                            * ET[i]<0 in case I-th extrema is a maximum
+            NE          -   number of extrema, >=0
+            DE          -   is set to True in case there is at least one interval
+                            where spline is a constant. Such degenerate cases are  
+                            not reported in the E/NE.
+                            
+        NOTES:
+
+        1. This function does NOT report following kinds of roots:
+           * intervals where function is constantly zero
+           * roots which are outside of [A,B] (note: it CAN return A or B)
+
+        2. This function does NOT report following kinds of extrema:
+           * intervals where function is a constant
+           * extrema which are outside of (A,B) (note: it WON'T return A or B)
+           
+         -- ALGLIB PROJECT --
+             Copyright 26.09.2011 by Bochkanov Sergey   
+        *************************************************************************/
+        public static void spline1drootsandextrema(spline1dinterpolant c,
+            ref double[] r,
+            ref int nr,
+            ref bool dr,
+            ref double[] e,
+            ref int[] et,
+            ref int ne,
+            ref bool de)
+        {
+            double pl = 0;
+            double ml = 0;
+            double pll = 0;
+            double pr = 0;
+            double mr = 0;
+            double[] tr = new double[0];
+            double[] tmpr = new double[0];
+            double[] tmpe = new double[0];
+            int[] tmpet = new int[0];
+            double[] tmpc = new double[0];
+            double x0 = 0;
+            double x1 = 0;
+            double x2 = 0;
+            double ex0 = 0;
+            double ex1 = 0;
+            int tne = 0;
+            int tnr = 0;
+            int i = 0;
+            int j = 0;
+            bool nstep = new bool();
+
+            r = new double[0];
+            nr = 0;
+            dr = new bool();
+            e = new double[0];
+            et = new int[0];
+            ne = 0;
+            de = new bool();
+
+            
+            //
+            //exception handling
+            //
+            alglib.ap.assert(c.k==3, "Spline1DRootsAndExtrema : incorrect parameter C.K!");
+            alglib.ap.assert(c.continuity>=0, "Spline1DRootsAndExtrema : parameter C.Continuity must not be less than 0!");
+            
+            //
+            //initialization of variable
+            //
+            nr = 0;
+            ne = 0;
+            dr = false;
+            de = false;
+            nstep = true;
+            
+            //
+            //consider case, when C.Continuty=0
+            //
+            if( c.continuity==0 )
+            {
+                
+                //
+                //allocation for auxiliary arrays 
+                //'TmpR ' - it stores a time value for roots
+                //'TmpE ' - it stores a time value for extremums
+                //'TmpET '- it stores a time value for extremums type
+                //
+                apserv.rvectorsetlengthatleast(ref tmpr, 3*(c.n-1));
+                apserv.rvectorsetlengthatleast(ref tmpe, 2*(c.n-1));
+                apserv.ivectorsetlengthatleast(ref tmpet, 2*(c.n-1));
+                
+                //
+                //start calculating
+                //
+                for(i=0; i<=c.n-2; i++)
+                {
+                    
+                    //
+                    //initialization pL, mL, pR, mR
+                    //
+                    pl = c.c[4*i];
+                    ml = c.c[4*i+1];
+                    pr = c.c[4*(i+1)];
+                    mr = c.c[4*i+1]+2*c.c[4*i+2]*(c.x[i+1]-c.x[i])+3*c.c[4*i+3]*(c.x[i+1]-c.x[i])*(c.x[i+1]-c.x[i]);
+                    
+                    //
+                    //pre-searching roots and extremums
+                    //
+                    solvecubicpolinom(pl, ml, pr, mr, c.x[i], c.x[i+1], ref x0, ref x1, ref x2, ref ex0, ref ex1, ref tnr, ref tne, ref tr);
+                    dr = dr || tnr==-1;
+                    de = de || tne==-1;
+                    
+                    //
+                    //searching of roots
+                    //
+                    if( tnr==1 && nstep )
+                    {
+                        
+                        //
+                        //is there roots?
+                        //
+                        if( nr>0 )
+                        {
+                            
+                            //
+                            //is a next root equal a previous root?
+                            //if is't, then write new root
+                            //
+                            if( (double)(x0)!=(double)(tmpr[nr-1]) )
+                            {
+                                tmpr[nr] = x0;
+                                nr = nr+1;
+                            }
+                        }
+                        else
+                        {
+                            
+                            //
+                            //write a first root
+                            //
+                            tmpr[nr] = x0;
+                            nr = nr+1;
+                        }
+                    }
+                    else
+                    {
+                        
+                        //
+                        //case when function at a segment identically to zero
+                        //then we have to clear a root, if the one located on a 
+                        //constant segment
+                        //
+                        if( tnr==-1 )
+                        {
+                            
+                            //
+                            //safe state variable as constant
+                            //
+                            if( nstep )
+                            {
+                                nstep = false;
+                            }
+                            
+                            //
+                            //clear the root, if there is
+                            //
+                            if( nr>0 )
+                            {
+                                if( (double)(c.x[i])==(double)(tmpr[nr-1]) )
+                                {
+                                    nr = nr-1;
+                                }
+                            }
+                            
+                            //
+                            //change state for 'DR'
+                            //
+                            if( !dr )
+                            {
+                                dr = true;
+                            }
+                        }
+                        else
+                        {
+                            nstep = true;
+                        }
+                    }
+                    
+                    //
+                    //searching of extremums
+                    //
+                    if( i>0 )
+                    {
+                        pll = c.c[4*(i-1)];
+                        
+                        //
+                        //if pL=pLL or pL=pR then
+                        //
+                        if( tne==-1 )
+                        {
+                            if( !de )
+                            {
+                                de = true;
+                            }
+                        }
+                        else
+                        {
+                            if( (double)(pl)>(double)(pll) && (double)(pl)>(double)(pr) )
+                            {
+                                
+                                //
+                                //maximum
+                                //
+                                tmpet[ne] = -1;
+                                tmpe[ne] = c.x[i];
+                                ne = ne+1;
+                            }
+                            else
+                            {
+                                if( (double)(pl)<(double)(pll) && (double)(pl)<(double)(pr) )
+                                {
+                                    
+                                    //
+                                    //minimum
+                                    //
+                                    tmpet[ne] = 1;
+                                    tmpe[ne] = c.x[i];
+                                    ne = ne+1;
+                                }
+                            }
+                        }
+                    }
+                }
+                
+                //
+                //write final result
+                //
+                apserv.rvectorsetlengthatleast(ref r, nr);
+                apserv.rvectorsetlengthatleast(ref e, ne);
+                apserv.ivectorsetlengthatleast(ref et, ne);
+                
+                //
+                //write roots
+                //
+                for(i=0; i<=nr-1; i++)
+                {
+                    r[i] = tmpr[i];
+                }
+                
+                //
+                //write extremums and their types
+                //
+                for(i=0; i<=ne-1; i++)
+                {
+                    e[i] = tmpe[i];
+                    et[i] = tmpet[i];
+                }
+            }
+            else
+            {
+                
+                //
+                //case, when C.Continuity>=1 
+                //'TmpR ' - it stores a time value for roots
+                //'TmpC' - it stores a time value for extremums and 
+                //their function value (TmpC={EX0,F(EX0), EX1,F(EX1), ..., EXn,F(EXn)};)
+                //'TmpE' - it stores a time value for extremums only
+                //'TmpET'- it stores a time value for extremums type
+                //
+                apserv.rvectorsetlengthatleast(ref tmpr, 2*c.n-1);
+                apserv.rvectorsetlengthatleast(ref tmpc, 4*c.n);
+                apserv.rvectorsetlengthatleast(ref tmpe, 2*c.n);
+                apserv.ivectorsetlengthatleast(ref tmpet, 2*c.n);
+                
+                //
+                //start calculating
+                //
+                for(i=0; i<=c.n-2; i++)
+                {
+                    
+                    //
+                    //we calculate pL,mL, pR,mR as Fi+1(F'i+1) at left border
+                    //
+                    pl = c.c[4*i];
+                    ml = c.c[4*i+1];
+                    pr = c.c[4*(i+1)];
+                    mr = c.c[4*(i+1)+1];
+                    
+                    //
+                    //calculating roots and extremums at [X[i],X[i+1]]
+                    //
+                    solvecubicpolinom(pl, ml, pr, mr, c.x[i], c.x[i+1], ref x0, ref x1, ref x2, ref ex0, ref ex1, ref tnr, ref tne, ref tr);
+                    
+                    //
+                    //searching roots
+                    //
+                    if( tnr>0 )
+                    {
+                        
+                        //
+                        //re-init tR
+                        //
+                        if( tnr>=1 )
+                        {
+                            tr[0] = x0;
+                        }
+                        if( tnr>=2 )
+                        {
+                            tr[1] = x1;
+                        }
+                        if( tnr==3 )
+                        {
+                            tr[2] = x2;
+                        }
+                        
+                        //
+                        //start root selection
+                        //
+                        if( nr>0 )
+                        {
+                            if( (double)(tmpr[nr-1])!=(double)(x0) )
+                            {
+                                
+                                //
+                                //previous segment was't constant identical zero
+                                //
+                                if( nstep )
+                                {
+                                    for(j=0; j<=tnr-1; j++)
+                                    {
+                                        tmpr[nr+j] = tr[j];
+                                    }
+                                    nr = nr+tnr;
+                                }
+                                else
+                                {
+                                    
+                                    //
+                                    //previous segment was constant identical zero
+                                    //and we must ignore [NR+j-1] root
+                                    //
+                                    for(j=1; j<=tnr-1; j++)
+                                    {
+                                        tmpr[nr+j-1] = tr[j];
+                                    }
+                                    nr = nr+tnr-1;
+                                    nstep = true;
+                                }
+                            }
+                            else
+                            {
+                                for(j=1; j<=tnr-1; j++)
+                                {
+                                    tmpr[nr+j-1] = tr[j];
+                                }
+                                nr = nr+tnr-1;
+                            }
+                        }
+                        else
+                        {
+                            
+                            //
+                            //write first root
+                            //
+                            for(j=0; j<=tnr-1; j++)
+                            {
+                                tmpr[nr+j] = tr[j];
+                            }
+                            nr = nr+tnr;
+                        }
+                    }
+                    else
+                    {
+                        if( tnr==-1 )
+                        {
+                            
+                            //
+                            //decrement 'NR' if at previous step was writen a root
+                            //(previous segment identical zero)
+                            //
+                            if( nr>0 && nstep )
+                            {
+                                nr = nr-1;
+                            }
+                            
+                            //
+                            //previous segment is't constant
+                            //
+                            if( nstep )
+                            {
+                                nstep = false;
+                            }
+                            
+                            //
+                            //rewrite 'DR'
+                            //
+                            if( !dr )
+                            {
+                                dr = true;
+                            }
+                        }
+                    }
+                    
+                    //
+                    //searching extremums
+                    //write all term like extremums
+                    //
+                    if( tne==1 )
+                    {
+                        if( ne>0 )
+                        {
+                            
+                            //
+                            //just ignore identical extremums
+                            //because he must be one
+                            //
+                            if( (double)(tmpc[ne-2])!=(double)(ex0) )
+                            {
+                                tmpc[ne] = ex0;
+                                tmpc[ne+1] = c.c[4*i]+c.c[4*i+1]*(ex0-c.x[i])+c.c[4*i+2]*(ex0-c.x[i])*(ex0-c.x[i])+c.c[4*i+3]*(ex0-c.x[i])*(ex0-c.x[i])*(ex0-c.x[i]);
+                                ne = ne+2;
+                            }
+                        }
+                        else
+                        {
+                            
+                            //
+                            //write first extremum and it function value
+                            //
+                            tmpc[ne] = ex0;
+                            tmpc[ne+1] = c.c[4*i]+c.c[4*i+1]*(ex0-c.x[i])+c.c[4*i+2]*(ex0-c.x[i])*(ex0-c.x[i])+c.c[4*i+3]*(ex0-c.x[i])*(ex0-c.x[i])*(ex0-c.x[i]);
+                            ne = ne+2;
+                        }
+                    }
+                    else
+                    {
+                        if( tne==2 )
+                        {
+                            if( ne>0 )
+                            {
+                                
+                                //
+                                //ignore identical extremum
+                                //
+                                if( (double)(tmpc[ne-2])!=(double)(ex0) )
+                                {
+                                    tmpc[ne] = ex0;
+                                    tmpc[ne+1] = c.c[4*i]+c.c[4*i+1]*(ex0-c.x[i])+c.c[4*i+2]*(ex0-c.x[i])*(ex0-c.x[i])+c.c[4*i+3]*(ex0-c.x[i])*(ex0-c.x[i])*(ex0-c.x[i]);
+                                    ne = ne+2;
+                                }
+                            }
+                            else
+                            {
+                                
+                                //
+                                //write first extremum
+                                //
+                                tmpc[ne] = ex0;
+                                tmpc[ne+1] = c.c[4*i]+c.c[4*i+1]*(ex0-c.x[i])+c.c[4*i+2]*(ex0-c.x[i])*(ex0-c.x[i])+c.c[4*i+3]*(ex0-c.x[i])*(ex0-c.x[i])*(ex0-c.x[i]);
+                                ne = ne+2;
+                            }
+                            
+                            //
+                            //write second extremum
+                            //
+                            tmpc[ne] = ex1;
+                            tmpc[ne+1] = c.c[4*i]+c.c[4*i+1]*(ex1-c.x[i])+c.c[4*i+2]*(ex1-c.x[i])*(ex1-c.x[i])+c.c[4*i+3]*(ex1-c.x[i])*(ex1-c.x[i])*(ex1-c.x[i]);
+                            ne = ne+2;
+                        }
+                        else
+                        {
+                            if( tne==-1 )
+                            {
+                                if( !de )
+                                {
+                                    de = true;
+                                }
+                            }
+                        }
+                    }
+                }
+                
+                //
+                //checking of arrays
+                //get number of extremums (tNe=NE/2)
+                //initialize pL as value F0(X[0]) and
+                //initialize pR as value Fn-1(X[N])
+                //
+                tne = ne/2;
+                ne = 0;
+                pl = c.c[0];
+                pr = c.c[4*(c.n-1)];
+                for(i=0; i<=tne-1; i++)
+                {
+                    if( i>0 && i<tne-1 )
+                    {
+                        if( (double)(tmpc[2*i+1])>(double)(tmpc[2*(i-1)+1]) && (double)(tmpc[2*i+1])>(double)(tmpc[2*(i+1)+1]) )
+                        {
+                            
+                            //
+                            //maximum
+                            //
+                            tmpe[ne] = tmpc[2*i];
+                            tmpet[ne] = -1;
+                            ne = ne+1;
+                        }
+                        else
+                        {
+                            if( (double)(tmpc[2*i+1])<(double)(tmpc[2*(i-1)+1]) && (double)(tmpc[2*i+1])<(double)(tmpc[2*(i+1)+1]) )
+                            {
+                                
+                                //
+                                //minimum
+                                //
+                                tmpe[ne] = tmpc[2*i];
+                                tmpet[ne] = 1;
+                                ne = ne+1;
+                            }
+                        }
+                    }
+                    else
+                    {
+                        if( i==0 )
+                        {
+                            if( (double)(tmpc[2*i])!=(double)(c.x[0]) )
+                            {
+                                if( (double)(tmpc[2*i+1])>(double)(pl) && (double)(tmpc[2*i+1])>(double)(tmpc[2*(i+1)+1]) )
+                                {
+                                    
+                                    //
+                                    //maximum
+                                    //
+                                    tmpe[ne] = tmpc[2*i];
+                                    tmpet[ne] = -1;
+                                    ne = ne+1;
+                                }
+                                else
+                                {
+                                    if( (double)(tmpc[2*i+1])<(double)(pl) && (double)(tmpc[2*i+1])<(double)(tmpc[2*(i+1)+1]) )
+                                    {
+                                        
+                                        //
+                                        //minimum
+                                        //
+                                        tmpe[ne] = tmpc[2*i];
+                                        tmpet[ne] = 1;
+                                        ne = ne+1;
+                                    }
+                                }
+                            }
+                        }
+                        else
+                        {
+                            if( i==tne-1 )
+                            {
+                                if( (double)(tmpc[2*i])!=(double)(c.x[c.n-1]) )
+                                {
+                                    if( (double)(tmpc[2*i+1])>(double)(tmpc[2*(i-1)+1]) && (double)(tmpc[2*i+1])>(double)(pr) )
+                                    {
+                                        
+                                        //
+                                        //maximum
+                                        //
+                                        tmpe[ne] = tmpc[2*i];
+                                        tmpet[ne] = -1;
+                                        ne = ne+1;
+                                    }
+                                    else
+                                    {
+                                        if( (double)(tmpc[2*i+1])<(double)(tmpc[2*(i-1)+1]) && (double)(tmpc[2*i+1])<(double)(pr) )
+                                        {
+                                            
+                                            //
+                                            //minimum
+                                            //
+                                            tmpe[ne] = tmpc[2*i];
+                                            tmpet[ne] = 1;
+                                            ne = ne+1;
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+                
+                //
+                //final results
+                //allocate R, E, ET
+                //
+                apserv.rvectorsetlengthatleast(ref r, nr);
+                apserv.rvectorsetlengthatleast(ref e, ne);
+                apserv.ivectorsetlengthatleast(ref et, ne);
+                
+                //
+                //write result for extremus and their types
+                //
+                for(i=0; i<=ne-1; i++)
+                {
+                    e[i] = tmpe[i];
+                    et[i] = tmpet[i];
+                }
+                
+                //
+                //write result for roots
+                //
+                for(i=0; i<=nr-1; i++)
+                {
+                    r[i] = tmpr[i];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Internal subroutine. Heap sort.
+        *************************************************************************/
+        public static void heapsortdpoints(ref double[] x,
+            ref double[] y,
+            ref double[] d,
+            int n)
+        {
+            double[] rbuf = new double[0];
+            int[] ibuf = new int[0];
+            double[] rbuf2 = new double[0];
+            int[] ibuf2 = new int[0];
+            int i = 0;
+            int i_ = 0;
+
+            ibuf = new int[n];
+            rbuf = new double[n];
+            for(i=0; i<=n-1; i++)
+            {
+                ibuf[i] = i;
+            }
+            tsort.tagsortfasti(ref x, ref ibuf, ref rbuf2, ref ibuf2, n);
+            for(i=0; i<=n-1; i++)
+            {
+                rbuf[i] = y[ibuf[i]];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                y[i_] = rbuf[i_];
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                rbuf[i] = d[ibuf[i]];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                d[i_] = rbuf[i_];
+            }
+        }
+
+
+        /*************************************************************************
+        This procedure search roots of an quadratic equation inside [0;1] and it number of roots.
+
+        INPUT PARAMETERS:
+            P0   -   value of a function at 0
+            M0   -   value of a derivative at 0
+            P1   -   value of a function at 1
+            M1   -   value of a derivative at 1
+
+        OUTPUT PARAMETERS:
+            X0   -  first root of an equation
+            X1   -  second root of an equation
+            NR   -  number of roots
+            
+        RESTRICTIONS OF PARAMETERS:
+
+        Parameters for this procedure has't to be zero simultaneously. Is expected, 
+        that input polinom is't degenerate or constant identicaly ZERO.
+
+
+        REMARK:
+
+        The procedure always fill value for X1 and X2, even if it is't belongs to [0;1].
+        But first true root(even if existing one) is in X1.
+        Number of roots is NR.
+
+         -- ALGLIB PROJECT --
+             Copyright 26.09.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void solvepolinom2(double p0,
+            double m0,
+            double p1,
+            double m1,
+            ref double x0,
+            ref double x1,
+            ref int nr)
+        {
+            double a = 0;
+            double b = 0;
+            double c = 0;
+            double dd = 0;
+            double tmp = 0;
+            double exf = 0;
+            double extr = 0;
+
+            x0 = 0;
+            x1 = 0;
+            nr = 0;
+
+            
+            //
+            //calculate parameters for equation: A, B  and C
+            //
+            a = 6*p0+3*m0-6*p1+3*m1;
+            b = -(6*p0)-4*m0+6*p1-2*m1;
+            c = m0;
+            
+            //
+            //check case, when A=0
+            //we are considering the linear equation
+            //
+            if( (double)(a)==(double)(0) )
+            {
+                
+                //
+                //B<>0 and root inside [0;1]
+                //one root
+                //
+                if( ((double)(b)!=(double)(0) && Math.Sign(c)*Math.Sign(b)<=0) && (double)(Math.Abs(b))>=(double)(Math.Abs(c)) )
+                {
+                    x0 = -(c/b);
+                    nr = 1;
+                    return;
+                }
+                else
+                {
+                    nr = 0;
+                    return;
+                }
+            }
+            
+            //
+            //consider case, when extremumu outside (0;1)
+            //exist one root only
+            //
+            if( (double)(Math.Abs(2*a))<=(double)(Math.Abs(b)) || Math.Sign(b)*Math.Sign(a)>=0 )
+            {
+                if( Math.Sign(m0)*Math.Sign(m1)>0 )
+                {
+                    nr = 0;
+                    return;
+                }
+                
+                //
+                //consider case, when the one exist
+                //same sign of derivative
+                //
+                if( Math.Sign(m0)*Math.Sign(m1)<0 )
+                {
+                    nr = 1;
+                    extr = -(b/(2*a));
+                    dd = b*b-4*a*c;
+                    if( (double)(dd)<(double)(0) )
+                    {
+                        return;
+                    }
+                    x0 = (-b-Math.Sqrt(dd))/(2*a);
+                    x1 = (-b+Math.Sqrt(dd))/(2*a);
+                    if( ((double)(extr)>=(double)(1) && (double)(x1)<=(double)(extr)) || ((double)(extr)<=(double)(0) && (double)(x1)>=(double)(extr)) )
+                    {
+                        x0 = x1;
+                    }
+                    return;
+                }
+                
+                //
+                //consider case, when the one is 0
+                //
+                if( (double)(m0)==(double)(0) )
+                {
+                    x0 = 0;
+                    nr = 1;
+                    return;
+                }
+                if( (double)(m1)==(double)(0) )
+                {
+                    x0 = 1;
+                    nr = 1;
+                    return;
+                }
+            }
+            else
+            {
+                
+                //
+                //consider case, when both of derivatives is 0
+                //
+                if( (double)(m0)==(double)(0) && (double)(m1)==(double)(0) )
+                {
+                    x0 = 0;
+                    x1 = 1;
+                    nr = 2;
+                    return;
+                }
+                
+                //
+                //consider case, when derivative at 0 is 0, and derivative at 1 is't 0
+                //
+                if( (double)(m0)==(double)(0) && (double)(m1)!=(double)(0) )
+                {
+                    dd = b*b-4*a*c;
+                    if( (double)(dd)<(double)(0) )
+                    {
+                        x0 = 0;
+                        nr = 1;
+                        return;
+                    }
+                    x0 = (-b-Math.Sqrt(dd))/(2*a);
+                    x1 = (-b+Math.Sqrt(dd))/(2*a);
+                    extr = -(b/(2*a));
+                    exf = a*extr*extr+b*extr+c;
+                    if( Math.Sign(exf)*Math.Sign(m1)>0 )
+                    {
+                        x0 = 0;
+                        nr = 1;
+                        return;
+                    }
+                    else
+                    {
+                        if( (double)(extr)>(double)(x0) )
+                        {
+                            x0 = 0;
+                        }
+                        else
+                        {
+                            x1 = 0;
+                        }
+                        nr = 2;
+                        
+                        //
+                        //roots must placed ascending
+                        //
+                        if( (double)(x0)>(double)(x1) )
+                        {
+                            tmp = x0;
+                            x0 = x1;
+                            x1 = tmp;
+                        }
+                        return;
+                    }
+                }
+                if( (double)(m1)==(double)(0) && (double)(m0)!=(double)(0) )
+                {
+                    dd = b*b-4*a*c;
+                    if( (double)(dd)<(double)(0) )
+                    {
+                        x0 = 1;
+                        nr = 1;
+                        return;
+                    }
+                    x0 = (-b-Math.Sqrt(dd))/(2*a);
+                    x1 = (-b+Math.Sqrt(dd))/(2*a);
+                    extr = -(b/(2*a));
+                    exf = a*extr*extr+b*extr+c;
+                    if( Math.Sign(exf)*Math.Sign(m0)>0 )
+                    {
+                        x0 = 1;
+                        nr = 1;
+                        return;
+                    }
+                    else
+                    {
+                        if( (double)(extr)<(double)(x0) )
+                        {
+                            x0 = 1;
+                        }
+                        else
+                        {
+                            x1 = 1;
+                        }
+                        nr = 2;
+                        
+                        //
+                        //roots must placed ascending
+                        //
+                        if( (double)(x0)>(double)(x1) )
+                        {
+                            tmp = x0;
+                            x0 = x1;
+                            x1 = tmp;
+                        }
+                        return;
+                    }
+                }
+                else
+                {
+                    extr = -(b/(2*a));
+                    exf = a*extr*extr+b*extr+c;
+                    if( Math.Sign(exf)*Math.Sign(m0)>0 && Math.Sign(exf)*Math.Sign(m1)>0 )
+                    {
+                        nr = 0;
+                        return;
+                    }
+                    dd = b*b-4*a*c;
+                    if( (double)(dd)<(double)(0) )
+                    {
+                        nr = 0;
+                        return;
+                    }
+                    x0 = (-b-Math.Sqrt(dd))/(2*a);
+                    x1 = (-b+Math.Sqrt(dd))/(2*a);
+                    
+                    //
+                    //if EXF and m0, EXF and m1 has different signs, then equation has two roots              
+                    //
+                    if( Math.Sign(exf)*Math.Sign(m0)<0 && Math.Sign(exf)*Math.Sign(m1)<0 )
+                    {
+                        nr = 2;
+                        
+                        //
+                        //roots must placed ascending
+                        //
+                        if( (double)(x0)>(double)(x1) )
+                        {
+                            tmp = x0;
+                            x0 = x1;
+                            x1 = tmp;
+                        }
+                        return;
+                    }
+                    else
+                    {
+                        nr = 1;
+                        if( Math.Sign(exf)*Math.Sign(m0)<0 )
+                        {
+                            if( (double)(x1)<(double)(extr) )
+                            {
+                                x0 = x1;
+                            }
+                            return;
+                        }
+                        if( Math.Sign(exf)*Math.Sign(m1)<0 )
+                        {
+                            if( (double)(x1)>(double)(extr) )
+                            {
+                                x0 = x1;
+                            }
+                            return;
+                        }
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This procedure search roots of an cubic equation inside [A;B], it number of roots 
+        and number of extremums.
+
+        INPUT PARAMETERS:
+            pA   -   value of a function at A
+            mA   -   value of a derivative at A
+            pB   -   value of a function at B
+            mB   -   value of a derivative at B
+            A0   -   left border [A0;B0]
+            B0   -   right border [A0;B0]
+
+        OUTPUT PARAMETERS:
+            X0   -  first root of an equation
+            X1   -  second root of an equation
+            X2   -  third root of an equation
+            EX0  -  first extremum of a function
+            EX0  -  second extremum of a function
+            NR   -  number of roots
+            NR   -  number of extrmums
+            
+        RESTRICTIONS OF PARAMETERS:
+
+        Length of [A;B] must be positive and is't zero, i.e. A<>B and A<B.
+
+
+        REMARK:
+
+        If 'NR' is -1 it's mean, than polinom has infiniti roots.
+        If 'NE' is -1 it's mean, than polinom has infiniti extremums.
+
+         -- ALGLIB PROJECT --
+             Copyright 26.09.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void solvecubicpolinom(double pa,
+            double ma,
+            double pb,
+            double mb,
+            double a,
+            double b,
+            ref double x0,
+            ref double x1,
+            ref double x2,
+            ref double ex0,
+            ref double ex1,
+            ref int nr,
+            ref int ne,
+            ref double[] tempdata)
+        {
+            int i = 0;
+            double tmpma = 0;
+            double tmpmb = 0;
+            double tex0 = 0;
+            double tex1 = 0;
+
+            x0 = 0;
+            x1 = 0;
+            x2 = 0;
+            ex0 = 0;
+            ex1 = 0;
+            nr = 0;
+            ne = 0;
+
+            apserv.rvectorsetlengthatleast(ref tempdata, 3);
+            
+            //
+            //case, when A>B
+            //
+            alglib.ap.assert((double)(a)<(double)(b), "\nSolveCubicPolinom: incorrect borders for [A;B]!\n");
+            
+            //
+            //case 1    
+            //function can be identicaly to ZERO
+            //
+            if( (((double)(ma)==(double)(0) && (double)(mb)==(double)(0)) && (double)(pa)==(double)(pb)) && (double)(pa)==(double)(0) )
+            {
+                nr = -1;
+                ne = -1;
+                return;
+            }
+            if( ((double)(ma)==(double)(0) && (double)(mb)==(double)(0)) && (double)(pa)==(double)(pb) )
+            {
+                nr = 0;
+                ne = -1;
+                return;
+            }
+            tmpma = ma*(b-a);
+            tmpmb = mb*(b-a);
+            solvepolinom2(pa, tmpma, pb, tmpmb, ref ex0, ref ex1, ref ne);
+            ex0 = rescaleval(0, 1, a, b, ex0);
+            ex1 = rescaleval(0, 1, a, b, ex1);
+            
+            //
+            //case 3.1
+            //no extremums at [A;B]
+            //
+            if( ne==0 )
+            {
+                nr = bisectmethod(pa, tmpma, pb, tmpmb, 0, 1, ref x0);
+                if( nr==1 )
+                {
+                    x0 = rescaleval(0, 1, a, b, x0);
+                }
+                return;
+            }
+            
+            //
+            //case 3.2
+            //one extremum
+            //
+            if( ne==1 )
+            {
+                if( (double)(ex0)==(double)(a) || (double)(ex0)==(double)(b) )
+                {
+                    nr = bisectmethod(pa, tmpma, pb, tmpmb, 0, 1, ref x0);
+                    if( nr==1 )
+                    {
+                        x0 = rescaleval(0, 1, a, b, x0);
+                    }
+                    return;
+                }
+                else
+                {
+                    nr = 0;
+                    i = 0;
+                    tex0 = rescaleval(a, b, 0, 1, ex0);
+                    nr = bisectmethod(pa, tmpma, pb, tmpmb, 0, tex0, ref x0)+nr;
+                    if( nr>i )
+                    {
+                        tempdata[i] = rescaleval(0, tex0, a, ex0, x0);
+                        i = i+1;
+                    }
+                    nr = bisectmethod(pa, tmpma, pb, tmpmb, tex0, 1, ref x0)+nr;
+                    if( nr>i )
+                    {
+                        x0 = rescaleval(tex0, 1, ex0, b, x0);
+                        if( i>0 )
+                        {
+                            if( (double)(x0)!=(double)(tempdata[i-1]) )
+                            {
+                                tempdata[i] = x0;
+                                i = i+1;
+                            }
+                            else
+                            {
+                                nr = nr-1;
+                            }
+                        }
+                        else
+                        {
+                            tempdata[i] = x0;
+                            i = i+1;
+                        }
+                    }
+                    if( nr>0 )
+                    {
+                        x0 = tempdata[0];
+                        if( nr>1 )
+                        {
+                            x1 = tempdata[1];
+                        }
+                        return;
+                    }
+                }
+                return;
+            }
+            else
+            {
+                
+                //
+                //case 3.3
+                //two extremums(or more, but it's impossible)
+                //
+                //
+                //case 3.3.0
+                //both extremums at the border
+                //
+                if( (double)(ex0)==(double)(a) && (double)(ex1)==(double)(b) )
+                {
+                    nr = bisectmethod(pa, tmpma, pb, tmpmb, 0, 1, ref x0);
+                    if( nr==1 )
+                    {
+                        x0 = rescaleval(0, 1, a, b, x0);
+                    }
+                    return;
+                }
+                if( (double)(ex0)==(double)(a) && (double)(ex1)!=(double)(b) )
+                {
+                    nr = 0;
+                    i = 0;
+                    tex1 = rescaleval(a, b, 0, 1, ex1);
+                    nr = bisectmethod(pa, tmpma, pb, tmpmb, 0, tex1, ref x0)+nr;
+                    if( nr>i )
+                    {
+                        tempdata[i] = rescaleval(0, tex1, a, ex1, x0);
+                        i = i+1;
+                    }
+                    nr = bisectmethod(pa, tmpma, pb, tmpmb, tex1, 1, ref x0)+nr;
+                    if( nr>i )
+                    {
+                        x0 = rescaleval(tex1, 1, ex1, b, x0);
+                        if( (double)(x0)!=(double)(tempdata[i-1]) )
+                        {
+                            tempdata[i] = x0;
+                            i = i+1;
+                        }
+                        else
+                        {
+                            nr = nr-1;
+                        }
+                    }
+                    if( nr>0 )
+                    {
+                        x0 = tempdata[0];
+                        if( nr>1 )
+                        {
+                            x1 = tempdata[1];
+                        }
+                        return;
+                    }
+                }
+                if( (double)(ex1)==(double)(b) && (double)(ex0)!=(double)(a) )
+                {
+                    nr = 0;
+                    i = 0;
+                    tex0 = rescaleval(a, b, 0, 1, ex0);
+                    nr = bisectmethod(pa, tmpma, pb, tmpmb, 0, tex0, ref x0)+nr;
+                    if( nr>i )
+                    {
+                        tempdata[i] = rescaleval(0, tex0, a, ex0, x0);
+                        i = i+1;
+                    }
+                    nr = bisectmethod(pa, tmpma, pb, tmpmb, tex0, 1, ref x0)+nr;
+                    if( nr>i )
+                    {
+                        x0 = rescaleval(tex0, 1, ex0, b, x0);
+                        if( i>0 )
+                        {
+                            if( (double)(x0)!=(double)(tempdata[i-1]) )
+                            {
+                                tempdata[i] = x0;
+                                i = i+1;
+                            }
+                            else
+                            {
+                                nr = nr-1;
+                            }
+                        }
+                        else
+                        {
+                            tempdata[i] = x0;
+                            i = i+1;
+                        }
+                    }
+                    if( nr>0 )
+                    {
+                        x0 = tempdata[0];
+                        if( nr>1 )
+                        {
+                            x1 = tempdata[1];
+                        }
+                        return;
+                    }
+                }
+                else
+                {
+                    
+                    //
+                    //case 3.3.2
+                    //both extremums inside (0;1)
+                    //
+                    nr = 0;
+                    i = 0;
+                    tex0 = rescaleval(a, b, 0, 1, ex0);
+                    tex1 = rescaleval(a, b, 0, 1, ex1);
+                    nr = bisectmethod(pa, tmpma, pb, tmpmb, 0, tex0, ref x0)+nr;
+                    if( nr>i )
+                    {
+                        tempdata[i] = rescaleval(0, tex0, a, ex0, x0);
+                        i = i+1;
+                    }
+                    nr = bisectmethod(pa, tmpma, pb, tmpmb, tex0, tex1, ref x0)+nr;
+                    if( nr>i )
+                    {
+                        x0 = rescaleval(tex0, tex1, ex0, ex1, x0);
+                        if( i>0 )
+                        {
+                            if( (double)(x0)!=(double)(tempdata[i-1]) )
+                            {
+                                tempdata[i] = x0;
+                                i = i+1;
+                            }
+                            else
+                            {
+                                nr = nr-1;
+                            }
+                        }
+                        else
+                        {
+                            tempdata[i] = x0;
+                            i = i+1;
+                        }
+                    }
+                    nr = bisectmethod(pa, tmpma, pb, tmpmb, tex1, 1, ref x0)+nr;
+                    if( nr>i )
+                    {
+                        x0 = rescaleval(tex1, 1, ex1, b, x0);
+                        if( i>0 )
+                        {
+                            if( (double)(x0)!=(double)(tempdata[i-1]) )
+                            {
+                                tempdata[i] = x0;
+                                i = i+1;
+                            }
+                            else
+                            {
+                                nr = nr-1;
+                            }
+                        }
+                        else
+                        {
+                            tempdata[i] = x0;
+                            i = i+1;
+                        }
+                    }
+                    
+                    //
+                    //write are found roots
+                    //
+                    if( nr>0 )
+                    {
+                        x0 = tempdata[0];
+                        if( nr>1 )
+                        {
+                            x1 = tempdata[1];
+                        }
+                        if( nr>2 )
+                        {
+                            x2 = tempdata[2];
+                        }
+                        return;
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Function for searching a root at [A;B] by bisection method and return number of roots
+        (0 or 1)
+
+        INPUT PARAMETERS:
+            pA   -   value of a function at A
+            mA   -   value of a derivative at A
+            pB   -   value of a function at B
+            mB   -   value of a derivative at B
+            A0   -   left border [A0;B0] 
+            B0   -   right border [A0;B0]
+            
+        RESTRICTIONS OF PARAMETERS:
+
+        We assume, that B0>A0.
+
+
+        REMARK:
+
+        Assume, that exist one root only at [A;B], else 
+        function may be work incorrectly.
+        The function dont check value A0,B0!
+
+         -- ALGLIB PROJECT --
+             Copyright 26.09.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static int bisectmethod(double pa,
+            double ma,
+            double pb,
+            double mb,
+            double a,
+            double b,
+            ref double x)
+        {
+            int result = 0;
+            double vacuum = 0;
+            double eps = 0;
+            double a0 = 0;
+            double b0 = 0;
+            double m = 0;
+            double lf = 0;
+            double rf = 0;
+            double mf = 0;
+
+            x = 0;
+
+            
+            //
+            //accuracy
+            //
+            eps = 1000*(b-a)*math.machineepsilon;
+            
+            //
+            //initialization left and right borders
+            //
+            a0 = a;
+            b0 = b;
+            
+            //
+            //initialize function value at 'A' and 'B'
+            //
+            hermitecalc(pa, ma, pb, mb, a, ref lf, ref vacuum);
+            hermitecalc(pa, ma, pb, mb, b, ref rf, ref vacuum);
+            
+            //
+            //check, that 'A' and 'B' are't roots,
+            //and that root exist
+            //
+            if( Math.Sign(lf)*Math.Sign(rf)>0 )
+            {
+                result = 0;
+                return result;
+            }
+            else
+            {
+                if( (double)(lf)==(double)(0) )
+                {
+                    x = a;
+                    result = 1;
+                    return result;
+                }
+                else
+                {
+                    if( (double)(rf)==(double)(0) )
+                    {
+                        x = b;
+                        result = 1;
+                        return result;
+                    }
+                }
+            }
+            
+            //
+            //searching a root
+            //
+            do
+            {
+                m = (b0+a0)/2;
+                hermitecalc(pa, ma, pb, mb, a0, ref lf, ref vacuum);
+                hermitecalc(pa, ma, pb, mb, b0, ref rf, ref vacuum);
+                hermitecalc(pa, ma, pb, mb, m, ref mf, ref vacuum);
+                if( Math.Sign(mf)*Math.Sign(lf)<0 )
+                {
+                    b0 = m;
+                }
+                else
+                {
+                    if( Math.Sign(mf)*Math.Sign(rf)<0 )
+                    {
+                        a0 = m;
+                    }
+                    else
+                    {
+                        if( (double)(lf)==(double)(0) )
+                        {
+                            x = a0;
+                            result = 1;
+                            return result;
+                        }
+                        if( (double)(rf)==(double)(0) )
+                        {
+                            x = b0;
+                            result = 1;
+                            return result;
+                        }
+                        if( (double)(mf)==(double)(0) )
+                        {
+                            x = m;
+                            result = 1;
+                            return result;
+                        }
+                    }
+                }
+            }
+            while( (double)(Math.Abs(b0-a0))>=(double)(eps) );
+            x = m;
+            result = 1;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Internal version of Spline1DGridDiffCubic.
+
+        Accepts pre-ordered X/Y, temporary arrays (which may be  preallocated,  if
+        you want to save time, or not) and output array (which may be preallocated
+        too).
+
+        Y is passed as var-parameter because we may need to force last element  to
+        be equal to the first one (if periodic boundary conditions are specified).
+
+          -- ALGLIB PROJECT --
+             Copyright 03.09.2010 by Bochkanov Sergey
+        *************************************************************************/
+        private static void spline1dgriddiffcubicinternal(double[] x,
+            ref double[] y,
+            int n,
+            int boundltype,
+            double boundl,
+            int boundrtype,
+            double boundr,
+            ref double[] d,
+            ref double[] a1,
+            ref double[] a2,
+            ref double[] a3,
+            ref double[] b,
+            ref double[] dt)
+        {
+            int i = 0;
+            int i_ = 0;
+
+            
+            //
+            // allocate arrays
+            //
+            if( alglib.ap.len(d)<n )
+            {
+                d = new double[n];
+            }
+            if( alglib.ap.len(a1)<n )
+            {
+                a1 = new double[n];
+            }
+            if( alglib.ap.len(a2)<n )
+            {
+                a2 = new double[n];
+            }
+            if( alglib.ap.len(a3)<n )
+            {
+                a3 = new double[n];
+            }
+            if( alglib.ap.len(b)<n )
+            {
+                b = new double[n];
+            }
+            if( alglib.ap.len(dt)<n )
+            {
+                dt = new double[n];
+            }
+            
+            //
+            // Special cases:
+            // * N=2, parabolic terminated boundary condition on both ends
+            // * N=2, periodic boundary condition
+            //
+            if( (n==2 && boundltype==0) && boundrtype==0 )
+            {
+                d[0] = (y[1]-y[0])/(x[1]-x[0]);
+                d[1] = d[0];
+                return;
+            }
+            if( (n==2 && boundltype==-1) && boundrtype==-1 )
+            {
+                d[0] = 0;
+                d[1] = 0;
+                return;
+            }
+            
+            //
+            // Periodic and non-periodic boundary conditions are
+            // two separate classes
+            //
+            if( boundrtype==-1 && boundltype==-1 )
+            {
+                
+                //
+                // Periodic boundary conditions
+                //
+                y[n-1] = y[0];
+                
+                //
+                // Boundary conditions at N-1 points
+                // (one point less because last point is the same as first point).
+                //
+                a1[0] = x[1]-x[0];
+                a2[0] = 2*(x[1]-x[0]+x[n-1]-x[n-2]);
+                a3[0] = x[n-1]-x[n-2];
+                b[0] = 3*(y[n-1]-y[n-2])/(x[n-1]-x[n-2])*(x[1]-x[0])+3*(y[1]-y[0])/(x[1]-x[0])*(x[n-1]-x[n-2]);
+                for(i=1; i<=n-2; i++)
+                {
+                    
+                    //
+                    // Altough last point is [N-2], we use X[N-1] and Y[N-1]
+                    // (because of periodicity)
+                    //
+                    a1[i] = x[i+1]-x[i];
+                    a2[i] = 2*(x[i+1]-x[i-1]);
+                    a3[i] = x[i]-x[i-1];
+                    b[i] = 3*(y[i]-y[i-1])/(x[i]-x[i-1])*(x[i+1]-x[i])+3*(y[i+1]-y[i])/(x[i+1]-x[i])*(x[i]-x[i-1]);
+                }
+                
+                //
+                // Solve, add last point (with index N-1)
+                //
+                solvecyclictridiagonal(a1, a2, a3, b, n-1, ref dt);
+                for(i_=0; i_<=n-2;i_++)
+                {
+                    d[i_] = dt[i_];
+                }
+                d[n-1] = d[0];
+            }
+            else
+            {
+                
+                //
+                // Non-periodic boundary condition.
+                // Left boundary conditions.
+                //
+                if( boundltype==0 )
+                {
+                    a1[0] = 0;
+                    a2[0] = 1;
+                    a3[0] = 1;
+                    b[0] = 2*(y[1]-y[0])/(x[1]-x[0]);
+                }
+                if( boundltype==1 )
+                {
+                    a1[0] = 0;
+                    a2[0] = 1;
+                    a3[0] = 0;
+                    b[0] = boundl;
+                }
+                if( boundltype==2 )
+                {
+                    a1[0] = 0;
+                    a2[0] = 2;
+                    a3[0] = 1;
+                    b[0] = 3*(y[1]-y[0])/(x[1]-x[0])-0.5*boundl*(x[1]-x[0]);
+                }
+                
+                //
+                // Central conditions
+                //
+                for(i=1; i<=n-2; i++)
+                {
+                    a1[i] = x[i+1]-x[i];
+                    a2[i] = 2*(x[i+1]-x[i-1]);
+                    a3[i] = x[i]-x[i-1];
+                    b[i] = 3*(y[i]-y[i-1])/(x[i]-x[i-1])*(x[i+1]-x[i])+3*(y[i+1]-y[i])/(x[i+1]-x[i])*(x[i]-x[i-1]);
+                }
+                
+                //
+                // Right boundary conditions
+                //
+                if( boundrtype==0 )
+                {
+                    a1[n-1] = 1;
+                    a2[n-1] = 1;
+                    a3[n-1] = 0;
+                    b[n-1] = 2*(y[n-1]-y[n-2])/(x[n-1]-x[n-2]);
+                }
+                if( boundrtype==1 )
+                {
+                    a1[n-1] = 0;
+                    a2[n-1] = 1;
+                    a3[n-1] = 0;
+                    b[n-1] = boundr;
+                }
+                if( boundrtype==2 )
+                {
+                    a1[n-1] = 1;
+                    a2[n-1] = 2;
+                    a3[n-1] = 0;
+                    b[n-1] = 3*(y[n-1]-y[n-2])/(x[n-1]-x[n-2])+0.5*boundr*(x[n-1]-x[n-2]);
+                }
+                
+                //
+                // Solve
+                //
+                solvetridiagonal(a1, a2, a3, b, n, ref d);
+            }
+        }
+
+
+        /*************************************************************************
+        Internal subroutine. Heap sort.
+        *************************************************************************/
+        private static void heapsortpoints(ref double[] x,
+            ref double[] y,
+            int n)
+        {
+            double[] bufx = new double[0];
+            double[] bufy = new double[0];
+
+            tsort.tagsortfastr(ref x, ref y, ref bufx, ref bufy, n);
+        }
+
+
+        /*************************************************************************
+        Internal subroutine. Heap sort.
+
+        Accepts:
+            X, Y    -   points
+            P       -   empty or preallocated array
+            
+        Returns:
+            X, Y    -   sorted by X
+            P       -   array of permutations; I-th position of output
+                        arrays X/Y contains (X[P[I]],Y[P[I]])
+        *************************************************************************/
+        private static void heapsortppoints(ref double[] x,
+            ref double[] y,
+            ref int[] p,
+            int n)
+        {
+            double[] rbuf = new double[0];
+            int[] ibuf = new int[0];
+            int i = 0;
+            int i_ = 0;
+
+            if( alglib.ap.len(p)<n )
+            {
+                p = new int[n];
+            }
+            rbuf = new double[n];
+            for(i=0; i<=n-1; i++)
+            {
+                p[i] = i;
+            }
+            tsort.tagsortfasti(ref x, ref p, ref rbuf, ref ibuf, n);
+            for(i=0; i<=n-1; i++)
+            {
+                rbuf[i] = y[p[i]];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                y[i_] = rbuf[i_];
+            }
+        }
+
+
+        /*************************************************************************
+        Internal subroutine. Tridiagonal solver. Solves
+
+        ( B[0] C[0]                      
+        ( A[1] B[1] C[1]                 )
+        (      A[2] B[2] C[2]            )
+        (            ..........          ) * X = D
+        (            ..........          )
+        (           A[N-2] B[N-2] C[N-2] )
+        (                  A[N-1] B[N-1] )
+
+        *************************************************************************/
+        private static void solvetridiagonal(double[] a,
+            double[] b,
+            double[] c,
+            double[] d,
+            int n,
+            ref double[] x)
+        {
+            int k = 0;
+            double t = 0;
+
+            b = (double[])b.Clone();
+            d = (double[])d.Clone();
+
+            if( alglib.ap.len(x)<n )
+            {
+                x = new double[n];
+            }
+            for(k=1; k<=n-1; k++)
+            {
+                t = a[k]/b[k-1];
+                b[k] = b[k]-t*c[k-1];
+                d[k] = d[k]-t*d[k-1];
+            }
+            x[n-1] = d[n-1]/b[n-1];
+            for(k=n-2; k>=0; k--)
+            {
+                x[k] = (d[k]-c[k]*x[k+1])/b[k];
+            }
+        }
+
+
+        /*************************************************************************
+        Internal subroutine. Cyclic tridiagonal solver. Solves
+
+        ( B[0] C[0]                 A[0] )
+        ( A[1] B[1] C[1]                 )
+        (      A[2] B[2] C[2]            )
+        (            ..........          ) * X = D
+        (            ..........          )
+        (           A[N-2] B[N-2] C[N-2] )
+        ( C[N-1]           A[N-1] B[N-1] )
+        *************************************************************************/
+        private static void solvecyclictridiagonal(double[] a,
+            double[] b,
+            double[] c,
+            double[] d,
+            int n,
+            ref double[] x)
+        {
+            int k = 0;
+            double alpha = 0;
+            double beta = 0;
+            double gamma = 0;
+            double[] y = new double[0];
+            double[] z = new double[0];
+            double[] u = new double[0];
+
+            b = (double[])b.Clone();
+
+            if( alglib.ap.len(x)<n )
+            {
+                x = new double[n];
+            }
+            beta = a[0];
+            alpha = c[n-1];
+            gamma = -b[0];
+            b[0] = 2*b[0];
+            b[n-1] = b[n-1]-alpha*beta/gamma;
+            u = new double[n];
+            for(k=0; k<=n-1; k++)
+            {
+                u[k] = 0;
+            }
+            u[0] = gamma;
+            u[n-1] = alpha;
+            solvetridiagonal(a, b, c, d, n, ref y);
+            solvetridiagonal(a, b, c, u, n, ref z);
+            for(k=0; k<=n-1; k++)
+            {
+                x[k] = y[k]-(y[0]+beta/gamma*y[n-1])/(1+z[0]+beta/gamma*z[n-1])*z[k];
+            }
+        }
+
+
+        /*************************************************************************
+        Internal subroutine. Three-point differentiation
+        *************************************************************************/
+        private static double diffthreepoint(double t,
+            double x0,
+            double f0,
+            double x1,
+            double f1,
+            double x2,
+            double f2)
+        {
+            double result = 0;
+            double a = 0;
+            double b = 0;
+
+            t = t-x0;
+            x1 = x1-x0;
+            x2 = x2-x0;
+            a = (f2-f0-x2/x1*(f1-f0))/(math.sqr(x2)-x1*x2);
+            b = (f1-f0-a*math.sqr(x1))/x1;
+            result = 2*a*t+b;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Procedure for calculating value of a function is providet in the form of
+        Hermite polinom  
+
+        INPUT PARAMETERS:
+            P0   -   value of a function at 0
+            M0   -   value of a derivative at 0
+            P1   -   value of a function at 1
+            M1   -   value of a derivative at 1
+            T    -   point inside [0;1]
+            
+        OUTPUT PARAMETERS:
+            S    -   value of a function at T
+            B0   -   value of a derivative function at T
+            
+         -- ALGLIB PROJECT --
+             Copyright 26.09.2011 by Bochkanov Sergey
+        *************************************************************************/
+        private static void hermitecalc(double p0,
+            double m0,
+            double p1,
+            double m1,
+            double t,
+            ref double s,
+            ref double ds)
+        {
+            s = 0;
+            ds = 0;
+
+            s = p0*(1+2*t)*(1-t)*(1-t)+m0*t*(1-t)*(1-t)+p1*(3-2*t)*t*t+m1*t*t*(t-1);
+            ds = -(p0*6*t*(1-t))+m0*(1-t)*(1-3*t)+p1*6*t*(1-t)+m1*t*(3*t-2);
+        }
+
+
+        /*************************************************************************
+        Function for mapping from [A0;B0] to [A1;B1]
+
+        INPUT PARAMETERS:
+            A0   -   left border [A0;B0]
+            B0   -   right border [A0;B0]
+            A1   -   left border [A1;B1]
+            B1   -   right border [A1;B1]
+            T    -   value inside [A0;B0]  
+            
+        RESTRICTIONS OF PARAMETERS:
+
+        We assume, that B0>A0 and B1>A1. But we chech, that T is inside [A0;B0], 
+        and if T<A0 then T become A1, if T>B0 then T - B1. 
+
+        INPUT PARAMETERS:
+                A0   -   left border for segment [A0;B0] from 'T' is converted to [A1;B1] 
+                B0   -   right border for segment [A0;B0] from 'T' is converted to [A1;B1] 
+                A1   -   left border for segment [A1;B1] to 'T' is converted from [A0;B0] 
+                B1   -   right border for segment [A1;B1] to 'T' is converted from [A0;B0] 
+                T    -   the parameter is mapped from [A0;B0] to [A1;B1] 
+
+        Result:
+            is converted value for 'T' from [A0;B0] to [A1;B1]
+                 
+        REMARK:
+
+        The function dont check value A0,B0 and A1,B1!
+
+         -- ALGLIB PROJECT --
+             Copyright 26.09.2011 by Bochkanov Sergey
+        *************************************************************************/
+        private static double rescaleval(double a0,
+            double b0,
+            double a1,
+            double b1,
+            double t)
+        {
+            double result = 0;
+
+            
+            //
+            //return left border
+            //
+            if( (double)(t)<=(double)(a0) )
+            {
+                result = a1;
+                return result;
+            }
+            
+            //
+            //return right border
+            //
+            if( (double)(t)>=(double)(b0) )
+            {
+                result = b1;
+                return result;
+            }
+            
+            //
+            //return value between left and right borders
+            //
+            result = (b1-a1)*(t-a0)/(b0-a0)+a1;
+            return result;
+        }
+
+
+    }
+    public class lsfit
+    {
+        /*************************************************************************
+        Polynomial fitting report:
+            TaskRCond       reciprocal of task's condition number
+            RMSError        RMS error
+            AvgError        average error
+            AvgRelError     average relative error (for non-zero Y[I])
+            MaxError        maximum error
+        *************************************************************************/
+        public class polynomialfitreport
+        {
+            public double taskrcond;
+            public double rmserror;
+            public double avgerror;
+            public double avgrelerror;
+            public double maxerror;
+        };
+
+
+        /*************************************************************************
+        Barycentric fitting report:
+            RMSError        RMS error
+            AvgError        average error
+            AvgRelError     average relative error (for non-zero Y[I])
+            MaxError        maximum error
+            TaskRCond       reciprocal of task's condition number
+        *************************************************************************/
+        public class barycentricfitreport
+        {
+            public double taskrcond;
+            public int dbest;
+            public double rmserror;
+            public double avgerror;
+            public double avgrelerror;
+            public double maxerror;
+        };
+
+
+        /*************************************************************************
+        Spline fitting report:
+            RMSError        RMS error
+            AvgError        average error
+            AvgRelError     average relative error (for non-zero Y[I])
+            MaxError        maximum error
+            
+        Fields  below are  filled  by   obsolete    functions   (Spline1DFitCubic,
+        Spline1DFitHermite). Modern fitting functions do NOT fill these fields:
+            TaskRCond       reciprocal of task's condition number
+        *************************************************************************/
+        public class spline1dfitreport
+        {
+            public double taskrcond;
+            public double rmserror;
+            public double avgerror;
+            public double avgrelerror;
+            public double maxerror;
+        };
+
+
+        /*************************************************************************
+        Least squares fitting report:
+            TaskRCond       reciprocal of task's condition number
+            IterationsCount number of internal iterations
+
+            RMSError        RMS error
+            AvgError        average error
+            AvgRelError     average relative error (for non-zero Y[I])
+            MaxError        maximum error
+
+            WRMSError       weighted RMS error
+        *************************************************************************/
+        public class lsfitreport
+        {
+            public double taskrcond;
+            public int iterationscount;
+            public double rmserror;
+            public double avgerror;
+            public double avgrelerror;
+            public double maxerror;
+            public double wrmserror;
+        };
+
+
+        /*************************************************************************
+        Nonlinear fitter.
+
+        You should use ALGLIB functions to work with fitter.
+        Never try to access its fields directly!
+        *************************************************************************/
+        public class lsfitstate
+        {
+            public int optalgo;
+            public int m;
+            public int k;
+            public double epsf;
+            public double epsx;
+            public int maxits;
+            public double stpmax;
+            public bool xrep;
+            public double[] s;
+            public double[] bndl;
+            public double[] bndu;
+            public double[,] taskx;
+            public double[] tasky;
+            public int npoints;
+            public double[] w;
+            public int nweights;
+            public int wkind;
+            public int wits;
+            public bool xupdated;
+            public bool needf;
+            public bool needfg;
+            public bool needfgh;
+            public int pointindex;
+            public double[] x;
+            public double[] c;
+            public double f;
+            public double[] g;
+            public double[,] h;
+            public int repiterationscount;
+            public int repterminationtype;
+            public double reprmserror;
+            public double repavgerror;
+            public double repavgrelerror;
+            public double repmaxerror;
+            public double repwrmserror;
+            public minlm.minlmstate optstate;
+            public minlm.minlmreport optrep;
+            public int prevnpt;
+            public int prevalgo;
+            public rcommstate rstate;
+            public lsfitstate()
+            {
+                s = new double[0];
+                bndl = new double[0];
+                bndu = new double[0];
+                taskx = new double[0,0];
+                tasky = new double[0];
+                w = new double[0];
+                x = new double[0];
+                c = new double[0];
+                g = new double[0];
+                h = new double[0,0];
+                optstate = new minlm.minlmstate();
+                optrep = new minlm.minlmreport();
+                rstate = new rcommstate();
+            }
+        };
+
+
+
+
+        public const int rfsmax = 10;
+
+
+        /*************************************************************************
+        Fitting by polynomials in barycentric form. This function provides  simple
+        unterface for unconstrained unweighted fitting. See  PolynomialFitWC()  if
+        you need constrained fitting.
+
+        Task is linear, so linear least squares solver is used. Complexity of this
+        computational scheme is O(N*M^2), mostly dominated by least squares solver
+
+        SEE ALSO:
+            PolynomialFitWC()
+
+        INPUT PARAMETERS:
+            X   -   points, array[0..N-1].
+            Y   -   function values, array[0..N-1].
+            N   -   number of points, N>0
+                    * if given, only leading N elements of X/Y are used
+                    * if not given, automatically determined from sizes of X/Y
+            M   -   number of basis functions (= polynomial_degree + 1), M>=1
+
+        OUTPUT PARAMETERS:
+            Info-   same format as in LSFitLinearW() subroutine:
+                    * Info>0    task is solved
+                    * Info<=0   an error occured:
+                                -4 means inconvergence of internal SVD
+            P   -   interpolant in barycentric form.
+            Rep -   report, same format as in LSFitLinearW() subroutine.
+                    Following fields are set:
+                    * RMSError      rms error on the (X,Y).
+                    * AvgError      average error on the (X,Y).
+                    * AvgRelError   average relative error on the non-zero Y
+                    * MaxError      maximum error
+                                    NON-WEIGHTED ERRORS ARE CALCULATED
+
+        NOTES:
+            you can convert P from barycentric form  to  the  power  or  Chebyshev
+            basis with PolynomialBar2Pow() or PolynomialBar2Cheb() functions  from
+            POLINT subpackage.
+
+          -- ALGLIB PROJECT --
+             Copyright 10.12.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void polynomialfit(double[] x,
+            double[] y,
+            int n,
+            int m,
+            ref int info,
+            ratint.barycentricinterpolant p,
+            polynomialfitreport rep)
+        {
+            int i = 0;
+            double[] w = new double[0];
+            double[] xc = new double[0];
+            double[] yc = new double[0];
+            int[] dc = new int[0];
+
+            info = 0;
+
+            alglib.ap.assert(n>0, "PolynomialFit: N<=0!");
+            alglib.ap.assert(m>0, "PolynomialFit: M<=0!");
+            alglib.ap.assert(alglib.ap.len(x)>=n, "PolynomialFit: Length(X)<N!");
+            alglib.ap.assert(alglib.ap.len(y)>=n, "PolynomialFit: Length(Y)<N!");
+            alglib.ap.assert(apserv.isfinitevector(x, n), "PolynomialFit: X contains infinite or NaN values!");
+            alglib.ap.assert(apserv.isfinitevector(y, n), "PolynomialFit: Y contains infinite or NaN values!");
+            w = new double[n];
+            for(i=0; i<=n-1; i++)
+            {
+                w[i] = 1;
+            }
+            polynomialfitwc(x, y, w, n, xc, yc, dc, 0, m, ref info, p, rep);
+        }
+
+
+        /*************************************************************************
+        Weighted  fitting by polynomials in barycentric form, with constraints  on
+        function values or first derivatives.
+
+        Small regularizing term is used when solving constrained tasks (to improve
+        stability).
+
+        Task is linear, so linear least squares solver is used. Complexity of this
+        computational scheme is O(N*M^2), mostly dominated by least squares solver
+
+        SEE ALSO:
+            PolynomialFit()
+
+        INPUT PARAMETERS:
+            X   -   points, array[0..N-1].
+            Y   -   function values, array[0..N-1].
+            W   -   weights, array[0..N-1]
+                    Each summand in square  sum  of  approximation deviations from
+                    given  values  is  multiplied  by  the square of corresponding
+                    weight. Fill it by 1's if you don't  want  to  solve  weighted
+                    task.
+            N   -   number of points, N>0.
+                    * if given, only leading N elements of X/Y/W are used
+                    * if not given, automatically determined from sizes of X/Y/W
+            XC  -   points where polynomial values/derivatives are constrained,
+                    array[0..K-1].
+            YC  -   values of constraints, array[0..K-1]
+            DC  -   array[0..K-1], types of constraints:
+                    * DC[i]=0   means that P(XC[i])=YC[i]
+                    * DC[i]=1   means that P'(XC[i])=YC[i]
+                    SEE BELOW FOR IMPORTANT INFORMATION ON CONSTRAINTS
+            K   -   number of constraints, 0<=K<M.
+                    K=0 means no constraints (XC/YC/DC are not used in such cases)
+            M   -   number of basis functions (= polynomial_degree + 1), M>=1
+
+        OUTPUT PARAMETERS:
+            Info-   same format as in LSFitLinearW() subroutine:
+                    * Info>0    task is solved
+                    * Info<=0   an error occured:
+                                -4 means inconvergence of internal SVD
+                                -3 means inconsistent constraints
+            P   -   interpolant in barycentric form.
+            Rep -   report, same format as in LSFitLinearW() subroutine.
+                    Following fields are set:
+                    * RMSError      rms error on the (X,Y).
+                    * AvgError      average error on the (X,Y).
+                    * AvgRelError   average relative error on the non-zero Y
+                    * MaxError      maximum error
+                                    NON-WEIGHTED ERRORS ARE CALCULATED
+
+        IMPORTANT:
+            this subroitine doesn't calculate task's condition number for K<>0.
+
+        NOTES:
+            you can convert P from barycentric form  to  the  power  or  Chebyshev
+            basis with PolynomialBar2Pow() or PolynomialBar2Cheb() functions  from
+            POLINT subpackage.
+
+        SETTING CONSTRAINTS - DANGERS AND OPPORTUNITIES:
+
+        Setting constraints can lead  to undesired  results,  like ill-conditioned
+        behavior, or inconsistency being detected. From the other side,  it allows
+        us to improve quality of the fit. Here we summarize  our  experience  with
+        constrained regression splines:
+        * even simple constraints can be inconsistent, see  Wikipedia  article  on
+          this subject: http://en.wikipedia.org/wiki/Birkhoff_interpolation
+        * the  greater  is  M (given  fixed  constraints),  the  more chances that
+          constraints will be consistent
+        * in the general case, consistency of constraints is NOT GUARANTEED.
+        * in the one special cases, however, we can  guarantee  consistency.  This
+          case  is:  M>1  and constraints on the function values (NOT DERIVATIVES)
+
+        Our final recommendation is to use constraints  WHEN  AND  ONLY  when  you
+        can't solve your task without them. Anything beyond  special  cases  given
+        above is not guaranteed and may result in inconsistency.
+
+          -- ALGLIB PROJECT --
+             Copyright 10.12.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void polynomialfitwc(double[] x,
+            double[] y,
+            double[] w,
+            int n,
+            double[] xc,
+            double[] yc,
+            int[] dc,
+            int k,
+            int m,
+            ref int info,
+            ratint.barycentricinterpolant p,
+            polynomialfitreport rep)
+        {
+            double xa = 0;
+            double xb = 0;
+            double sa = 0;
+            double sb = 0;
+            double[] xoriginal = new double[0];
+            double[] yoriginal = new double[0];
+            double[] y2 = new double[0];
+            double[] w2 = new double[0];
+            double[] tmp = new double[0];
+            double[] tmp2 = new double[0];
+            double[] bx = new double[0];
+            double[] by = new double[0];
+            double[] bw = new double[0];
+            int i = 0;
+            int j = 0;
+            double u = 0;
+            double v = 0;
+            double s = 0;
+            int relcnt = 0;
+            lsfitreport lrep = new lsfitreport();
+
+            x = (double[])x.Clone();
+            y = (double[])y.Clone();
+            w = (double[])w.Clone();
+            xc = (double[])xc.Clone();
+            yc = (double[])yc.Clone();
+            info = 0;
+
+            alglib.ap.assert(n>0, "PolynomialFitWC: N<=0!");
+            alglib.ap.assert(m>0, "PolynomialFitWC: M<=0!");
+            alglib.ap.assert(k>=0, "PolynomialFitWC: K<0!");
+            alglib.ap.assert(k<m, "PolynomialFitWC: K>=M!");
+            alglib.ap.assert(alglib.ap.len(x)>=n, "PolynomialFitWC: Length(X)<N!");
+            alglib.ap.assert(alglib.ap.len(y)>=n, "PolynomialFitWC: Length(Y)<N!");
+            alglib.ap.assert(alglib.ap.len(w)>=n, "PolynomialFitWC: Length(W)<N!");
+            alglib.ap.assert(alglib.ap.len(xc)>=k, "PolynomialFitWC: Length(XC)<K!");
+            alglib.ap.assert(alglib.ap.len(yc)>=k, "PolynomialFitWC: Length(YC)<K!");
+            alglib.ap.assert(alglib.ap.len(dc)>=k, "PolynomialFitWC: Length(DC)<K!");
+            alglib.ap.assert(apserv.isfinitevector(x, n), "PolynomialFitWC: X contains infinite or NaN values!");
+            alglib.ap.assert(apserv.isfinitevector(y, n), "PolynomialFitWC: Y contains infinite or NaN values!");
+            alglib.ap.assert(apserv.isfinitevector(w, n), "PolynomialFitWC: X contains infinite or NaN values!");
+            alglib.ap.assert(apserv.isfinitevector(xc, k), "PolynomialFitWC: XC contains infinite or NaN values!");
+            alglib.ap.assert(apserv.isfinitevector(yc, k), "PolynomialFitWC: YC contains infinite or NaN values!");
+            for(i=0; i<=k-1; i++)
+            {
+                alglib.ap.assert(dc[i]==0 || dc[i]==1, "PolynomialFitWC: one of DC[] is not 0 or 1!");
+            }
+            
+            //
+            // Scale X, Y, XC, YC.
+            // Solve scaled problem using internal Chebyshev fitting function.
+            //
+            lsfitscalexy(ref x, ref y, ref w, n, ref xc, ref yc, dc, k, ref xa, ref xb, ref sa, ref sb, ref xoriginal, ref yoriginal);
+            internalchebyshevfit(x, y, w, n, xc, yc, dc, k, m, ref info, ref tmp, lrep);
+            if( info<0 )
+            {
+                return;
+            }
+            
+            //
+            // Generate barycentric model and scale it
+            // * BX, BY store barycentric model nodes
+            // * FMatrix is reused (remember - it is at least MxM, what we need)
+            //
+            // Model intialization is done in O(M^2). In principle, it can be
+            // done in O(M*log(M)), but before it we solved task with O(N*M^2)
+            // complexity, so it is only a small amount of total time spent.
+            //
+            bx = new double[m];
+            by = new double[m];
+            bw = new double[m];
+            tmp2 = new double[m];
+            s = 1;
+            for(i=0; i<=m-1; i++)
+            {
+                if( m!=1 )
+                {
+                    u = Math.Cos(Math.PI*i/(m-1));
+                }
+                else
+                {
+                    u = 0;
+                }
+                v = 0;
+                for(j=0; j<=m-1; j++)
+                {
+                    if( j==0 )
+                    {
+                        tmp2[j] = 1;
+                    }
+                    else
+                    {
+                        if( j==1 )
+                        {
+                            tmp2[j] = u;
+                        }
+                        else
+                        {
+                            tmp2[j] = 2*u*tmp2[j-1]-tmp2[j-2];
+                        }
+                    }
+                    v = v+tmp[j]*tmp2[j];
+                }
+                bx[i] = u;
+                by[i] = v;
+                bw[i] = s;
+                if( i==0 || i==m-1 )
+                {
+                    bw[i] = 0.5*bw[i];
+                }
+                s = -s;
+            }
+            ratint.barycentricbuildxyw(bx, by, bw, m, p);
+            ratint.barycentriclintransx(p, 2/(xb-xa), -((xa+xb)/(xb-xa)));
+            ratint.barycentriclintransy(p, sb-sa, sa);
+            
+            //
+            // Scale absolute errors obtained from LSFitLinearW.
+            // Relative error should be calculated separately
+            // (because of shifting/scaling of the task)
+            //
+            rep.taskrcond = lrep.taskrcond;
+            rep.rmserror = lrep.rmserror*(sb-sa);
+            rep.avgerror = lrep.avgerror*(sb-sa);
+            rep.maxerror = lrep.maxerror*(sb-sa);
+            rep.avgrelerror = 0;
+            relcnt = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                if( (double)(yoriginal[i])!=(double)(0) )
+                {
+                    rep.avgrelerror = rep.avgrelerror+Math.Abs(ratint.barycentriccalc(p, xoriginal[i])-yoriginal[i])/Math.Abs(yoriginal[i]);
+                    relcnt = relcnt+1;
+                }
+            }
+            if( relcnt!=0 )
+            {
+                rep.avgrelerror = rep.avgrelerror/relcnt;
+            }
+        }
+
+
+        /*************************************************************************
+        Weghted rational least  squares  fitting  using  Floater-Hormann  rational
+        functions  with  optimal  D  chosen  from  [0,9],  with  constraints   and
+        individual weights.
+
+        Equidistant  grid  with M node on [min(x),max(x)]  is  used to build basis
+        functions. Different values of D are tried, optimal D (least WEIGHTED root
+        mean square error) is chosen.  Task  is  linear,  so  linear least squares
+        solver  is  used.  Complexity  of  this  computational  scheme is O(N*M^2)
+        (mostly dominated by the least squares solver).
+
+        SEE ALSO
+        * BarycentricFitFloaterHormann(), "lightweight" fitting without invididual
+          weights and constraints.
+
+        INPUT PARAMETERS:
+            X   -   points, array[0..N-1].
+            Y   -   function values, array[0..N-1].
+            W   -   weights, array[0..N-1]
+                    Each summand in square  sum  of  approximation deviations from
+                    given  values  is  multiplied  by  the square of corresponding
+                    weight. Fill it by 1's if you don't  want  to  solve  weighted
+                    task.
+            N   -   number of points, N>0.
+            XC  -   points where function values/derivatives are constrained,
+                    array[0..K-1].
+            YC  -   values of constraints, array[0..K-1]
+            DC  -   array[0..K-1], types of constraints:
+                    * DC[i]=0   means that S(XC[i])=YC[i]
+                    * DC[i]=1   means that S'(XC[i])=YC[i]
+                    SEE BELOW FOR IMPORTANT INFORMATION ON CONSTRAINTS
+            K   -   number of constraints, 0<=K<M.
+                    K=0 means no constraints (XC/YC/DC are not used in such cases)
+            M   -   number of basis functions ( = number_of_nodes), M>=2.
+
+        OUTPUT PARAMETERS:
+            Info-   same format as in LSFitLinearWC() subroutine.
+                    * Info>0    task is solved
+                    * Info<=0   an error occured:
+                                -4 means inconvergence of internal SVD
+                                -3 means inconsistent constraints
+                                -1 means another errors in parameters passed
+                                   (N<=0, for example)
+            B   -   barycentric interpolant.
+            Rep -   report, same format as in LSFitLinearWC() subroutine.
+                    Following fields are set:
+                    * DBest         best value of the D parameter
+                    * RMSError      rms error on the (X,Y).
+                    * AvgError      average error on the (X,Y).
+                    * AvgRelError   average relative error on the non-zero Y
+                    * MaxError      maximum error
+                                    NON-WEIGHTED ERRORS ARE CALCULATED
+
+        IMPORTANT:
+            this subroutine doesn't calculate task's condition number for K<>0.
+
+        SETTING CONSTRAINTS - DANGERS AND OPPORTUNITIES:
+
+        Setting constraints can lead  to undesired  results,  like ill-conditioned
+        behavior, or inconsistency being detected. From the other side,  it allows
+        us to improve quality of the fit. Here we summarize  our  experience  with
+        constrained barycentric interpolants:
+        * excessive  constraints  can  be  inconsistent.   Floater-Hormann   basis
+          functions aren't as flexible as splines (although they are very smooth).
+        * the more evenly constraints are spread across [min(x),max(x)],  the more
+          chances that they will be consistent
+        * the  greater  is  M (given  fixed  constraints),  the  more chances that
+          constraints will be consistent
+        * in the general case, consistency of constraints IS NOT GUARANTEED.
+        * in the several special cases, however, we CAN guarantee consistency.
+        * one of this cases is constraints on the function  VALUES at the interval
+          boundaries. Note that consustency of the  constraints  on  the  function
+          DERIVATIVES is NOT guaranteed (you can use in such cases  cubic  splines
+          which are more flexible).
+        * another  special  case  is ONE constraint on the function value (OR, but
+          not AND, derivative) anywhere in the interval
+
+        Our final recommendation is to use constraints  WHEN  AND  ONLY  WHEN  you
+        can't solve your task without them. Anything beyond  special  cases  given
+        above is not guaranteed and may result in inconsistency.
+
+          -- ALGLIB PROJECT --
+             Copyright 18.08.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void barycentricfitfloaterhormannwc(double[] x,
+            double[] y,
+            double[] w,
+            int n,
+            double[] xc,
+            double[] yc,
+            int[] dc,
+            int k,
+            int m,
+            ref int info,
+            ratint.barycentricinterpolant b,
+            barycentricfitreport rep)
+        {
+            int d = 0;
+            int i = 0;
+            double wrmscur = 0;
+            double wrmsbest = 0;
+            ratint.barycentricinterpolant locb = new ratint.barycentricinterpolant();
+            barycentricfitreport locrep = new barycentricfitreport();
+            int locinfo = 0;
+
+            info = 0;
+
+            alglib.ap.assert(n>0, "BarycentricFitFloaterHormannWC: N<=0!");
+            alglib.ap.assert(m>0, "BarycentricFitFloaterHormannWC: M<=0!");
+            alglib.ap.assert(k>=0, "BarycentricFitFloaterHormannWC: K<0!");
+            alglib.ap.assert(k<m, "BarycentricFitFloaterHormannWC: K>=M!");
+            alglib.ap.assert(alglib.ap.len(x)>=n, "BarycentricFitFloaterHormannWC: Length(X)<N!");
+            alglib.ap.assert(alglib.ap.len(y)>=n, "BarycentricFitFloaterHormannWC: Length(Y)<N!");
+            alglib.ap.assert(alglib.ap.len(w)>=n, "BarycentricFitFloaterHormannWC: Length(W)<N!");
+            alglib.ap.assert(alglib.ap.len(xc)>=k, "BarycentricFitFloaterHormannWC: Length(XC)<K!");
+            alglib.ap.assert(alglib.ap.len(yc)>=k, "BarycentricFitFloaterHormannWC: Length(YC)<K!");
+            alglib.ap.assert(alglib.ap.len(dc)>=k, "BarycentricFitFloaterHormannWC: Length(DC)<K!");
+            alglib.ap.assert(apserv.isfinitevector(x, n), "BarycentricFitFloaterHormannWC: X contains infinite or NaN values!");
+            alglib.ap.assert(apserv.isfinitevector(y, n), "BarycentricFitFloaterHormannWC: Y contains infinite or NaN values!");
+            alglib.ap.assert(apserv.isfinitevector(w, n), "BarycentricFitFloaterHormannWC: X contains infinite or NaN values!");
+            alglib.ap.assert(apserv.isfinitevector(xc, k), "BarycentricFitFloaterHormannWC: XC contains infinite or NaN values!");
+            alglib.ap.assert(apserv.isfinitevector(yc, k), "BarycentricFitFloaterHormannWC: YC contains infinite or NaN values!");
+            for(i=0; i<=k-1; i++)
+            {
+                alglib.ap.assert(dc[i]==0 || dc[i]==1, "BarycentricFitFloaterHormannWC: one of DC[] is not 0 or 1!");
+            }
+            
+            //
+            // Find optimal D
+            //
+            // Info is -3 by default (degenerate constraints).
+            // If LocInfo will always be equal to -3, Info will remain equal to -3.
+            // If at least once LocInfo will be -4, Info will be -4.
+            //
+            wrmsbest = math.maxrealnumber;
+            rep.dbest = -1;
+            info = -3;
+            for(d=0; d<=Math.Min(9, n-1); d++)
+            {
+                barycentricfitwcfixedd(x, y, w, n, xc, yc, dc, k, m, d, ref locinfo, locb, locrep);
+                alglib.ap.assert((locinfo==-4 || locinfo==-3) || locinfo>0, "BarycentricFitFloaterHormannWC: unexpected result from BarycentricFitWCFixedD!");
+                if( locinfo>0 )
+                {
+                    
+                    //
+                    // Calculate weghted RMS
+                    //
+                    wrmscur = 0;
+                    for(i=0; i<=n-1; i++)
+                    {
+                        wrmscur = wrmscur+math.sqr(w[i]*(y[i]-ratint.barycentriccalc(locb, x[i])));
+                    }
+                    wrmscur = Math.Sqrt(wrmscur/n);
+                    if( (double)(wrmscur)<(double)(wrmsbest) || rep.dbest<0 )
+                    {
+                        ratint.barycentriccopy(locb, b);
+                        rep.dbest = d;
+                        info = 1;
+                        rep.rmserror = locrep.rmserror;
+                        rep.avgerror = locrep.avgerror;
+                        rep.avgrelerror = locrep.avgrelerror;
+                        rep.maxerror = locrep.maxerror;
+                        rep.taskrcond = locrep.taskrcond;
+                        wrmsbest = wrmscur;
+                    }
+                }
+                else
+                {
+                    if( locinfo!=-3 && info<0 )
+                    {
+                        info = locinfo;
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Rational least squares fitting using  Floater-Hormann  rational  functions
+        with optimal D chosen from [0,9].
+
+        Equidistant  grid  with M node on [min(x),max(x)]  is  used to build basis
+        functions. Different values of D are tried, optimal  D  (least  root  mean
+        square error) is chosen.  Task  is  linear, so linear least squares solver
+        is used. Complexity  of  this  computational  scheme is  O(N*M^2)  (mostly
+        dominated by the least squares solver).
+
+        INPUT PARAMETERS:
+            X   -   points, array[0..N-1].
+            Y   -   function values, array[0..N-1].
+            N   -   number of points, N>0.
+            M   -   number of basis functions ( = number_of_nodes), M>=2.
+
+        OUTPUT PARAMETERS:
+            Info-   same format as in LSFitLinearWC() subroutine.
+                    * Info>0    task is solved
+                    * Info<=0   an error occured:
+                                -4 means inconvergence of internal SVD
+                                -3 means inconsistent constraints
+            B   -   barycentric interpolant.
+            Rep -   report, same format as in LSFitLinearWC() subroutine.
+                    Following fields are set:
+                    * DBest         best value of the D parameter
+                    * RMSError      rms error on the (X,Y).
+                    * AvgError      average error on the (X,Y).
+                    * AvgRelError   average relative error on the non-zero Y
+                    * MaxError      maximum error
+                                    NON-WEIGHTED ERRORS ARE CALCULATED
+
+          -- ALGLIB PROJECT --
+             Copyright 18.08.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void barycentricfitfloaterhormann(double[] x,
+            double[] y,
+            int n,
+            int m,
+            ref int info,
+            ratint.barycentricinterpolant b,
+            barycentricfitreport rep)
+        {
+            double[] w = new double[0];
+            double[] xc = new double[0];
+            double[] yc = new double[0];
+            int[] dc = new int[0];
+            int i = 0;
+
+            info = 0;
+
+            alglib.ap.assert(n>0, "BarycentricFitFloaterHormann: N<=0!");
+            alglib.ap.assert(m>0, "BarycentricFitFloaterHormann: M<=0!");
+            alglib.ap.assert(alglib.ap.len(x)>=n, "BarycentricFitFloaterHormann: Length(X)<N!");
+            alglib.ap.assert(alglib.ap.len(y)>=n, "BarycentricFitFloaterHormann: Length(Y)<N!");
+            alglib.ap.assert(apserv.isfinitevector(x, n), "BarycentricFitFloaterHormann: X contains infinite or NaN values!");
+            alglib.ap.assert(apserv.isfinitevector(y, n), "BarycentricFitFloaterHormann: Y contains infinite or NaN values!");
+            w = new double[n];
+            for(i=0; i<=n-1; i++)
+            {
+                w[i] = 1;
+            }
+            barycentricfitfloaterhormannwc(x, y, w, n, xc, yc, dc, 0, m, ref info, b, rep);
+        }
+
+
+        /*************************************************************************
+        Rational least squares fitting using  Floater-Hormann  rational  functions
+        with optimal D chosen from [0,9].
+
+        Equidistant  grid  with M node on [min(x),max(x)]  is  used to build basis
+        functions. Different values of D are tried, optimal  D  (least  root  mean
+        square error) is chosen.  Task  is  linear, so linear least squares solver
+        is used. Complexity  of  this  computational  scheme is  O(N*M^2)  (mostly
+        dominated by the least squares solver).
+
+        INPUT PARAMETERS:
+            X   -   points, array[0..N-1].
+            Y   -   function values, array[0..N-1].
+            N   -   number of points, N>0.
+            M   -   number of basis functions ( = number_of_nodes), M>=2.
+
+        OUTPUT PARAMETERS:
+            Info-   same format as in LSFitLinearWC() subroutine.
+                    * Info>0    task is solved
+                    * Info<=0   an error occured:
+                                -4 means inconvergence of internal SVD
+                                -3 means inconsistent constraints
+            B   -   barycentric interpolant.
+            Rep -   report, same format as in LSFitLinearWC() subroutine.
+                    Following fields are set:
+                    * DBest         best value of the D parameter
+                    * RMSError      rms error on the (X,Y).
+                    * AvgError      average error on the (X,Y).
+                    * AvgRelError   average relative error on the non-zero Y
+                    * MaxError      maximum error
+                                    NON-WEIGHTED ERRORS ARE CALCULATED
+
+          -- ALGLIB PROJECT --
+             Copyright 18.08.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spline1dfitpenalized(double[] x,
+            double[] y,
+            int n,
+            int m,
+            double rho,
+            ref int info,
+            spline1d.spline1dinterpolant s,
+            spline1dfitreport rep)
+        {
+            double[] w = new double[0];
+            int i = 0;
+
+            x = (double[])x.Clone();
+            y = (double[])y.Clone();
+            info = 0;
+
+            alglib.ap.assert(n>=1, "Spline1DFitPenalized: N<1!");
+            alglib.ap.assert(m>=4, "Spline1DFitPenalized: M<4!");
+            alglib.ap.assert(alglib.ap.len(x)>=n, "Spline1DFitPenalized: Length(X)<N!");
+            alglib.ap.assert(alglib.ap.len(y)>=n, "Spline1DFitPenalized: Length(Y)<N!");
+            alglib.ap.assert(apserv.isfinitevector(x, n), "Spline1DFitPenalized: X contains infinite or NAN values!");
+            alglib.ap.assert(apserv.isfinitevector(y, n), "Spline1DFitPenalized: Y contains infinite or NAN values!");
+            alglib.ap.assert(math.isfinite(rho), "Spline1DFitPenalized: Rho is infinite!");
+            w = new double[n];
+            for(i=0; i<=n-1; i++)
+            {
+                w[i] = 1;
+            }
+            spline1dfitpenalizedw(x, y, w, n, m, rho, ref info, s, rep);
+        }
+
+
+        /*************************************************************************
+        Weighted fitting by penalized cubic spline.
+
+        Equidistant grid with M nodes on [min(x,xc),max(x,xc)] is  used  to  build
+        basis functions. Basis functions are cubic splines with  natural  boundary
+        conditions. Problem is regularized by  adding non-linearity penalty to the
+        usual least squares penalty function:
+
+            S(x) = arg min { LS + P }, where
+            LS   = SUM { w[i]^2*(y[i] - S(x[i]))^2 } - least squares penalty
+            P    = C*10^rho*integral{ S''(x)^2*dx } - non-linearity penalty
+            rho  - tunable constant given by user
+            C    - automatically determined scale parameter,
+                   makes penalty invariant with respect to scaling of X, Y, W.
+
+        INPUT PARAMETERS:
+            X   -   points, array[0..N-1].
+            Y   -   function values, array[0..N-1].
+            W   -   weights, array[0..N-1]
+                    Each summand in square  sum  of  approximation deviations from
+                    given  values  is  multiplied  by  the square of corresponding
+                    weight. Fill it by 1's if you don't  want  to  solve  weighted
+                    problem.
+            N   -   number of points (optional):
+                    * N>0
+                    * if given, only first N elements of X/Y/W are processed
+                    * if not given, automatically determined from X/Y/W sizes
+            M   -   number of basis functions ( = number_of_nodes), M>=4.
+            Rho -   regularization  constant  passed   by   user.   It   penalizes
+                    nonlinearity in the regression spline. It  is  logarithmically
+                    scaled,  i.e.  actual  value  of  regularization  constant  is
+                    calculated as 10^Rho. It is automatically scaled so that:
+                    * Rho=2.0 corresponds to moderate amount of nonlinearity
+                    * generally, it should be somewhere in the [-8.0,+8.0]
+                    If you do not want to penalize nonlineary,
+                    pass small Rho. Values as low as -15 should work.
+
+        OUTPUT PARAMETERS:
+            Info-   same format as in LSFitLinearWC() subroutine.
+                    * Info>0    task is solved
+                    * Info<=0   an error occured:
+                                -4 means inconvergence of internal SVD or
+                                   Cholesky decomposition; problem may be
+                                   too ill-conditioned (very rare)
+            S   -   spline interpolant.
+            Rep -   Following fields are set:
+                    * RMSError      rms error on the (X,Y).
+                    * AvgError      average error on the (X,Y).
+                    * AvgRelError   average relative error on the non-zero Y
+                    * MaxError      maximum error
+                                    NON-WEIGHTED ERRORS ARE CALCULATED
+
+        IMPORTANT:
+            this subroitine doesn't calculate task's condition number for K<>0.
+
+        NOTE 1: additional nodes are added to the spline outside  of  the  fitting
+        interval to force linearity when x<min(x,xc) or x>max(x,xc).  It  is  done
+        for consistency - we penalize non-linearity  at [min(x,xc),max(x,xc)],  so
+        it is natural to force linearity outside of this interval.
+
+        NOTE 2: function automatically sorts points,  so  caller may pass unsorted
+        array.
+
+          -- ALGLIB PROJECT --
+             Copyright 19.10.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spline1dfitpenalizedw(double[] x,
+            double[] y,
+            double[] w,
+            int n,
+            int m,
+            double rho,
+            ref int info,
+            spline1d.spline1dinterpolant s,
+            spline1dfitreport rep)
+        {
+            int i = 0;
+            int j = 0;
+            int b = 0;
+            double v = 0;
+            double relcnt = 0;
+            double xa = 0;
+            double xb = 0;
+            double sa = 0;
+            double sb = 0;
+            double[] xoriginal = new double[0];
+            double[] yoriginal = new double[0];
+            double pdecay = 0;
+            double tdecay = 0;
+            double[,] fmatrix = new double[0,0];
+            double[] fcolumn = new double[0];
+            double[] y2 = new double[0];
+            double[] w2 = new double[0];
+            double[] xc = new double[0];
+            double[] yc = new double[0];
+            int[] dc = new int[0];
+            double fdmax = 0;
+            double admax = 0;
+            double[,] amatrix = new double[0,0];
+            double[,] d2matrix = new double[0,0];
+            double fa = 0;
+            double ga = 0;
+            double fb = 0;
+            double gb = 0;
+            double lambdav = 0;
+            double[] bx = new double[0];
+            double[] by = new double[0];
+            double[] bd1 = new double[0];
+            double[] bd2 = new double[0];
+            double[] tx = new double[0];
+            double[] ty = new double[0];
+            double[] td = new double[0];
+            spline1d.spline1dinterpolant bs = new spline1d.spline1dinterpolant();
+            double[,] nmatrix = new double[0,0];
+            double[] rightpart = new double[0];
+            fbls.fblslincgstate cgstate = new fbls.fblslincgstate();
+            double[] c = new double[0];
+            double[] tmp0 = new double[0];
+            int i_ = 0;
+            int i1_ = 0;
+
+            x = (double[])x.Clone();
+            y = (double[])y.Clone();
+            w = (double[])w.Clone();
+            info = 0;
+
+            alglib.ap.assert(n>=1, "Spline1DFitPenalizedW: N<1!");
+            alglib.ap.assert(m>=4, "Spline1DFitPenalizedW: M<4!");
+            alglib.ap.assert(alglib.ap.len(x)>=n, "Spline1DFitPenalizedW: Length(X)<N!");
+            alglib.ap.assert(alglib.ap.len(y)>=n, "Spline1DFitPenalizedW: Length(Y)<N!");
+            alglib.ap.assert(alglib.ap.len(w)>=n, "Spline1DFitPenalizedW: Length(W)<N!");
+            alglib.ap.assert(apserv.isfinitevector(x, n), "Spline1DFitPenalizedW: X contains infinite or NAN values!");
+            alglib.ap.assert(apserv.isfinitevector(y, n), "Spline1DFitPenalizedW: Y contains infinite or NAN values!");
+            alglib.ap.assert(apserv.isfinitevector(w, n), "Spline1DFitPenalizedW: Y contains infinite or NAN values!");
+            alglib.ap.assert(math.isfinite(rho), "Spline1DFitPenalizedW: Rho is infinite!");
+            
+            //
+            // Prepare LambdaV
+            //
+            v = -(Math.Log(math.machineepsilon)/Math.Log(10));
+            if( (double)(rho)<(double)(-v) )
+            {
+                rho = -v;
+            }
+            if( (double)(rho)>(double)(v) )
+            {
+                rho = v;
+            }
+            lambdav = Math.Pow(10, rho);
+            
+            //
+            // Sort X, Y, W
+            //
+            spline1d.heapsortdpoints(ref x, ref y, ref w, n);
+            
+            //
+            // Scale X, Y, XC, YC
+            //
+            lsfitscalexy(ref x, ref y, ref w, n, ref xc, ref yc, dc, 0, ref xa, ref xb, ref sa, ref sb, ref xoriginal, ref yoriginal);
+            
+            //
+            // Allocate space
+            //
+            fmatrix = new double[n, m];
+            amatrix = new double[m, m];
+            d2matrix = new double[m, m];
+            bx = new double[m];
+            by = new double[m];
+            fcolumn = new double[n];
+            nmatrix = new double[m, m];
+            rightpart = new double[m];
+            tmp0 = new double[Math.Max(m, n)];
+            c = new double[m];
+            
+            //
+            // Fill:
+            // * FMatrix by values of basis functions
+            // * TmpAMatrix by second derivatives of I-th function at J-th point
+            // * CMatrix by constraints
+            //
+            fdmax = 0;
+            for(b=0; b<=m-1; b++)
+            {
+                
+                //
+                // Prepare I-th basis function
+                //
+                for(j=0; j<=m-1; j++)
+                {
+                    bx[j] = (double)(2*j)/(double)(m-1)-1;
+                    by[j] = 0;
+                }
+                by[b] = 1;
+                spline1d.spline1dgriddiff2cubic(bx, by, m, 2, 0.0, 2, 0.0, ref bd1, ref bd2);
+                spline1d.spline1dbuildcubic(bx, by, m, 2, 0.0, 2, 0.0, bs);
+                
+                //
+                // Calculate B-th column of FMatrix
+                // Update FDMax (maximum column norm)
+                //
+                spline1d.spline1dconvcubic(bx, by, m, 2, 0.0, 2, 0.0, x, n, ref fcolumn);
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    fmatrix[i_,b] = fcolumn[i_];
+                }
+                v = 0;
+                for(i=0; i<=n-1; i++)
+                {
+                    v = v+math.sqr(w[i]*fcolumn[i]);
+                }
+                fdmax = Math.Max(fdmax, v);
+                
+                //
+                // Fill temporary with second derivatives of basis function
+                //
+                for(i_=0; i_<=m-1;i_++)
+                {
+                    d2matrix[b,i_] = bd2[i_];
+                }
+            }
+            
+            //
+            // * calculate penalty matrix A
+            // * calculate max of diagonal elements of A
+            // * calculate PDecay - coefficient before penalty matrix
+            //
+            for(i=0; i<=m-1; i++)
+            {
+                for(j=i; j<=m-1; j++)
+                {
+                    
+                    //
+                    // calculate integral(B_i''*B_j'') where B_i and B_j are
+                    // i-th and j-th basis splines.
+                    // B_i and B_j are piecewise linear functions.
+                    //
+                    v = 0;
+                    for(b=0; b<=m-2; b++)
+                    {
+                        fa = d2matrix[i,b];
+                        fb = d2matrix[i,b+1];
+                        ga = d2matrix[j,b];
+                        gb = d2matrix[j,b+1];
+                        v = v+(bx[b+1]-bx[b])*(fa*ga+(fa*(gb-ga)+ga*(fb-fa))/2+(fb-fa)*(gb-ga)/3);
+                    }
+                    amatrix[i,j] = v;
+                    amatrix[j,i] = v;
+                }
+            }
+            admax = 0;
+            for(i=0; i<=m-1; i++)
+            {
+                admax = Math.Max(admax, Math.Abs(amatrix[i,i]));
+            }
+            pdecay = lambdav*fdmax/admax;
+            
+            //
+            // Calculate TDecay for Tikhonov regularization
+            //
+            tdecay = fdmax*(1+pdecay)*10*math.machineepsilon;
+            
+            //
+            // Prepare system
+            //
+            // NOTE: FMatrix is spoiled during this process
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                v = w[i];
+                for(i_=0; i_<=m-1;i_++)
+                {
+                    fmatrix[i,i_] = v*fmatrix[i,i_];
+                }
+            }
+            ablas.rmatrixgemm(m, m, n, 1.0, fmatrix, 0, 0, 1, fmatrix, 0, 0, 0, 0.0, ref nmatrix, 0, 0);
+            for(i=0; i<=m-1; i++)
+            {
+                for(j=0; j<=m-1; j++)
+                {
+                    nmatrix[i,j] = nmatrix[i,j]+pdecay*amatrix[i,j];
+                }
+            }
+            for(i=0; i<=m-1; i++)
+            {
+                nmatrix[i,i] = nmatrix[i,i]+tdecay;
+            }
+            for(i=0; i<=m-1; i++)
+            {
+                rightpart[i] = 0;
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                v = y[i]*w[i];
+                for(i_=0; i_<=m-1;i_++)
+                {
+                    rightpart[i_] = rightpart[i_] + v*fmatrix[i,i_];
+                }
+            }
+            
+            //
+            // Solve system
+            //
+            if( !trfac.spdmatrixcholesky(ref nmatrix, m, true) )
+            {
+                info = -4;
+                return;
+            }
+            fbls.fblscholeskysolve(nmatrix, 1.0, m, true, ref rightpart, ref tmp0);
+            for(i_=0; i_<=m-1;i_++)
+            {
+                c[i_] = rightpart[i_];
+            }
+            
+            //
+            // add nodes to force linearity outside of the fitting interval
+            //
+            spline1d.spline1dgriddiffcubic(bx, c, m, 2, 0.0, 2, 0.0, ref bd1);
+            tx = new double[m+2];
+            ty = new double[m+2];
+            td = new double[m+2];
+            i1_ = (0) - (1);
+            for(i_=1; i_<=m;i_++)
+            {
+                tx[i_] = bx[i_+i1_];
+            }
+            i1_ = (0) - (1);
+            for(i_=1; i_<=m;i_++)
+            {
+                ty[i_] = rightpart[i_+i1_];
+            }
+            i1_ = (0) - (1);
+            for(i_=1; i_<=m;i_++)
+            {
+                td[i_] = bd1[i_+i1_];
+            }
+            tx[0] = tx[1]-(tx[2]-tx[1]);
+            ty[0] = ty[1]-td[1]*(tx[2]-tx[1]);
+            td[0] = td[1];
+            tx[m+1] = tx[m]+(tx[m]-tx[m-1]);
+            ty[m+1] = ty[m]+td[m]*(tx[m]-tx[m-1]);
+            td[m+1] = td[m];
+            spline1d.spline1dbuildhermite(tx, ty, td, m+2, s);
+            spline1d.spline1dlintransx(s, 2/(xb-xa), -((xa+xb)/(xb-xa)));
+            spline1d.spline1dlintransy(s, sb-sa, sa);
+            info = 1;
+            
+            //
+            // Fill report
+            //
+            rep.rmserror = 0;
+            rep.avgerror = 0;
+            rep.avgrelerror = 0;
+            rep.maxerror = 0;
+            relcnt = 0;
+            spline1d.spline1dconvcubic(bx, rightpart, m, 2, 0.0, 2, 0.0, x, n, ref fcolumn);
+            for(i=0; i<=n-1; i++)
+            {
+                v = (sb-sa)*fcolumn[i]+sa;
+                rep.rmserror = rep.rmserror+math.sqr(v-yoriginal[i]);
+                rep.avgerror = rep.avgerror+Math.Abs(v-yoriginal[i]);
+                if( (double)(yoriginal[i])!=(double)(0) )
+                {
+                    rep.avgrelerror = rep.avgrelerror+Math.Abs(v-yoriginal[i])/Math.Abs(yoriginal[i]);
+                    relcnt = relcnt+1;
+                }
+                rep.maxerror = Math.Max(rep.maxerror, Math.Abs(v-yoriginal[i]));
+            }
+            rep.rmserror = Math.Sqrt(rep.rmserror/n);
+            rep.avgerror = rep.avgerror/n;
+            if( (double)(relcnt)!=(double)(0) )
+            {
+                rep.avgrelerror = rep.avgrelerror/relcnt;
+            }
+        }
+
+
+        /*************************************************************************
+        Weighted fitting by cubic  spline,  with constraints on function values or
+        derivatives.
+
+        Equidistant grid with M-2 nodes on [min(x,xc),max(x,xc)] is  used to build
+        basis functions. Basis functions are cubic splines with continuous  second
+        derivatives  and  non-fixed first  derivatives  at  interval  ends.  Small
+        regularizing term is used  when  solving  constrained  tasks  (to  improve
+        stability).
+
+        Task is linear, so linear least squares solver is used. Complexity of this
+        computational scheme is O(N*M^2), mostly dominated by least squares solver
+
+        SEE ALSO
+            Spline1DFitHermiteWC()  -   fitting by Hermite splines (more flexible,
+                                        less smooth)
+            Spline1DFitCubic()      -   "lightweight" fitting  by  cubic  splines,
+                                        without invididual weights and constraints
+
+        INPUT PARAMETERS:
+            X   -   points, array[0..N-1].
+            Y   -   function values, array[0..N-1].
+            W   -   weights, array[0..N-1]
+                    Each summand in square  sum  of  approximation deviations from
+                    given  values  is  multiplied  by  the square of corresponding
+                    weight. Fill it by 1's if you don't  want  to  solve  weighted
+                    task.
+            N   -   number of points (optional):
+                    * N>0
+                    * if given, only first N elements of X/Y/W are processed
+                    * if not given, automatically determined from X/Y/W sizes
+            XC  -   points where spline values/derivatives are constrained,
+                    array[0..K-1].
+            YC  -   values of constraints, array[0..K-1]
+            DC  -   array[0..K-1], types of constraints:
+                    * DC[i]=0   means that S(XC[i])=YC[i]
+                    * DC[i]=1   means that S'(XC[i])=YC[i]
+                    SEE BELOW FOR IMPORTANT INFORMATION ON CONSTRAINTS
+            K   -   number of constraints (optional):
+                    * 0<=K<M.
+                    * K=0 means no constraints (XC/YC/DC are not used)
+                    * if given, only first K elements of XC/YC/DC are used
+                    * if not given, automatically determined from XC/YC/DC
+            M   -   number of basis functions ( = number_of_nodes+2), M>=4.
+
+        OUTPUT PARAMETERS:
+            Info-   same format as in LSFitLinearWC() subroutine.
+                    * Info>0    task is solved
+                    * Info<=0   an error occured:
+                                -4 means inconvergence of internal SVD
+                                -3 means inconsistent constraints
+            S   -   spline interpolant.
+            Rep -   report, same format as in LSFitLinearWC() subroutine.
+                    Following fields are set:
+                    * RMSError      rms error on the (X,Y).
+                    * AvgError      average error on the (X,Y).
+                    * AvgRelError   average relative error on the non-zero Y
+                    * MaxError      maximum error
+                                    NON-WEIGHTED ERRORS ARE CALCULATED
+
+        IMPORTANT:
+            this subroitine doesn't calculate task's condition number for K<>0.
+
+
+        ORDER OF POINTS
+
+        Subroutine automatically sorts points, so caller may pass unsorted array.
+
+        SETTING CONSTRAINTS - DANGERS AND OPPORTUNITIES:
+
+        Setting constraints can lead  to undesired  results,  like ill-conditioned
+        behavior, or inconsistency being detected. From the other side,  it allows
+        us to improve quality of the fit. Here we summarize  our  experience  with
+        constrained regression splines:
+        * excessive constraints can be inconsistent. Splines are  piecewise  cubic
+          functions, and it is easy to create an example, where  large  number  of
+          constraints  concentrated  in  small  area will result in inconsistency.
+          Just because spline is not flexible enough to satisfy all of  them.  And
+          same constraints spread across the  [min(x),max(x)]  will  be  perfectly
+          consistent.
+        * the more evenly constraints are spread across [min(x),max(x)],  the more
+          chances that they will be consistent
+        * the  greater  is  M (given  fixed  constraints),  the  more chances that
+          constraints will be consistent
+        * in the general case, consistency of constraints IS NOT GUARANTEED.
+        * in the several special cases, however, we CAN guarantee consistency.
+        * one of this cases is constraints  on  the  function  values  AND/OR  its
+          derivatives at the interval boundaries.
+        * another  special  case  is ONE constraint on the function value (OR, but
+          not AND, derivative) anywhere in the interval
+
+        Our final recommendation is to use constraints  WHEN  AND  ONLY  WHEN  you
+        can't solve your task without them. Anything beyond  special  cases  given
+        above is not guaranteed and may result in inconsistency.
+
+
+          -- ALGLIB PROJECT --
+             Copyright 18.08.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spline1dfitcubicwc(double[] x,
+            double[] y,
+            double[] w,
+            int n,
+            double[] xc,
+            double[] yc,
+            int[] dc,
+            int k,
+            int m,
+            ref int info,
+            spline1d.spline1dinterpolant s,
+            spline1dfitreport rep)
+        {
+            int i = 0;
+
+            info = 0;
+
+            alglib.ap.assert(n>=1, "Spline1DFitCubicWC: N<1!");
+            alglib.ap.assert(m>=4, "Spline1DFitCubicWC: M<4!");
+            alglib.ap.assert(k>=0, "Spline1DFitCubicWC: K<0!");
+            alglib.ap.assert(k<m, "Spline1DFitCubicWC: K>=M!");
+            alglib.ap.assert(alglib.ap.len(x)>=n, "Spline1DFitCubicWC: Length(X)<N!");
+            alglib.ap.assert(alglib.ap.len(y)>=n, "Spline1DFitCubicWC: Length(Y)<N!");
+            alglib.ap.assert(alglib.ap.len(w)>=n, "Spline1DFitCubicWC: Length(W)<N!");
+            alglib.ap.assert(alglib.ap.len(xc)>=k, "Spline1DFitCubicWC: Length(XC)<K!");
+            alglib.ap.assert(alglib.ap.len(yc)>=k, "Spline1DFitCubicWC: Length(YC)<K!");
+            alglib.ap.assert(alglib.ap.len(dc)>=k, "Spline1DFitCubicWC: Length(DC)<K!");
+            alglib.ap.assert(apserv.isfinitevector(x, n), "Spline1DFitCubicWC: X contains infinite or NAN values!");
+            alglib.ap.assert(apserv.isfinitevector(y, n), "Spline1DFitCubicWC: Y contains infinite or NAN values!");
+            alglib.ap.assert(apserv.isfinitevector(w, n), "Spline1DFitCubicWC: Y contains infinite or NAN values!");
+            alglib.ap.assert(apserv.isfinitevector(xc, k), "Spline1DFitCubicWC: X contains infinite or NAN values!");
+            alglib.ap.assert(apserv.isfinitevector(yc, k), "Spline1DFitCubicWC: Y contains infinite or NAN values!");
+            for(i=0; i<=k-1; i++)
+            {
+                alglib.ap.assert(dc[i]==0 || dc[i]==1, "Spline1DFitCubicWC: DC[i] is neither 0 or 1!");
+            }
+            spline1dfitinternal(0, x, y, w, n, xc, yc, dc, k, m, ref info, s, rep);
+        }
+
+
+        /*************************************************************************
+        Weighted  fitting  by Hermite spline,  with constraints on function values
+        or first derivatives.
+
+        Equidistant grid with M nodes on [min(x,xc),max(x,xc)] is  used  to  build
+        basis functions. Basis functions are Hermite splines.  Small  regularizing
+        term is used when solving constrained tasks (to improve stability).
+
+        Task is linear, so linear least squares solver is used. Complexity of this
+        computational scheme is O(N*M^2), mostly dominated by least squares solver
+
+        SEE ALSO
+            Spline1DFitCubicWC()    -   fitting by Cubic splines (less flexible,
+                                        more smooth)
+            Spline1DFitHermite()    -   "lightweight" Hermite fitting, without
+                                        invididual weights and constraints
+
+        INPUT PARAMETERS:
+            X   -   points, array[0..N-1].
+            Y   -   function values, array[0..N-1].
+            W   -   weights, array[0..N-1]
+                    Each summand in square  sum  of  approximation deviations from
+                    given  values  is  multiplied  by  the square of corresponding
+                    weight. Fill it by 1's if you don't  want  to  solve  weighted
+                    task.
+            N   -   number of points (optional):
+                    * N>0
+                    * if given, only first N elements of X/Y/W are processed
+                    * if not given, automatically determined from X/Y/W sizes
+            XC  -   points where spline values/derivatives are constrained,
+                    array[0..K-1].
+            YC  -   values of constraints, array[0..K-1]
+            DC  -   array[0..K-1], types of constraints:
+                    * DC[i]=0   means that S(XC[i])=YC[i]
+                    * DC[i]=1   means that S'(XC[i])=YC[i]
+                    SEE BELOW FOR IMPORTANT INFORMATION ON CONSTRAINTS
+            K   -   number of constraints (optional):
+                    * 0<=K<M.
+                    * K=0 means no constraints (XC/YC/DC are not used)
+                    * if given, only first K elements of XC/YC/DC are used
+                    * if not given, automatically determined from XC/YC/DC
+            M   -   number of basis functions (= 2 * number of nodes),
+                    M>=4,
+                    M IS EVEN!
+
+        OUTPUT PARAMETERS:
+            Info-   same format as in LSFitLinearW() subroutine:
+                    * Info>0    task is solved
+                    * Info<=0   an error occured:
+                                -4 means inconvergence of internal SVD
+                                -3 means inconsistent constraints
+                                -2 means odd M was passed (which is not supported)
+                                -1 means another errors in parameters passed
+                                   (N<=0, for example)
+            S   -   spline interpolant.
+            Rep -   report, same format as in LSFitLinearW() subroutine.
+                    Following fields are set:
+                    * RMSError      rms error on the (X,Y).
+                    * AvgError      average error on the (X,Y).
+                    * AvgRelError   average relative error on the non-zero Y
+                    * MaxError      maximum error
+                                    NON-WEIGHTED ERRORS ARE CALCULATED
+
+        IMPORTANT:
+            this subroitine doesn't calculate task's condition number for K<>0.
+
+        IMPORTANT:
+            this subroitine supports only even M's
+
+
+        ORDER OF POINTS
+
+        Subroutine automatically sorts points, so caller may pass unsorted array.
+
+        SETTING CONSTRAINTS - DANGERS AND OPPORTUNITIES:
+
+        Setting constraints can lead  to undesired  results,  like ill-conditioned
+        behavior, or inconsistency being detected. From the other side,  it allows
+        us to improve quality of the fit. Here we summarize  our  experience  with
+        constrained regression splines:
+        * excessive constraints can be inconsistent. Splines are  piecewise  cubic
+          functions, and it is easy to create an example, where  large  number  of
+          constraints  concentrated  in  small  area will result in inconsistency.
+          Just because spline is not flexible enough to satisfy all of  them.  And
+          same constraints spread across the  [min(x),max(x)]  will  be  perfectly
+          consistent.
+        * the more evenly constraints are spread across [min(x),max(x)],  the more
+          chances that they will be consistent
+        * the  greater  is  M (given  fixed  constraints),  the  more chances that
+          constraints will be consistent
+        * in the general case, consistency of constraints is NOT GUARANTEED.
+        * in the several special cases, however, we can guarantee consistency.
+        * one of this cases is  M>=4  and   constraints  on   the  function  value
+          (AND/OR its derivative) at the interval boundaries.
+        * another special case is M>=4  and  ONE  constraint on the function value
+          (OR, BUT NOT AND, derivative) anywhere in [min(x),max(x)]
+
+        Our final recommendation is to use constraints  WHEN  AND  ONLY  when  you
+        can't solve your task without them. Anything beyond  special  cases  given
+        above is not guaranteed and may result in inconsistency.
+
+          -- ALGLIB PROJECT --
+             Copyright 18.08.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spline1dfithermitewc(double[] x,
+            double[] y,
+            double[] w,
+            int n,
+            double[] xc,
+            double[] yc,
+            int[] dc,
+            int k,
+            int m,
+            ref int info,
+            spline1d.spline1dinterpolant s,
+            spline1dfitreport rep)
+        {
+            int i = 0;
+
+            info = 0;
+
+            alglib.ap.assert(n>=1, "Spline1DFitHermiteWC: N<1!");
+            alglib.ap.assert(m>=4, "Spline1DFitHermiteWC: M<4!");
+            alglib.ap.assert(m%2==0, "Spline1DFitHermiteWC: M is odd!");
+            alglib.ap.assert(k>=0, "Spline1DFitHermiteWC: K<0!");
+            alglib.ap.assert(k<m, "Spline1DFitHermiteWC: K>=M!");
+            alglib.ap.assert(alglib.ap.len(x)>=n, "Spline1DFitHermiteWC: Length(X)<N!");
+            alglib.ap.assert(alglib.ap.len(y)>=n, "Spline1DFitHermiteWC: Length(Y)<N!");
+            alglib.ap.assert(alglib.ap.len(w)>=n, "Spline1DFitHermiteWC: Length(W)<N!");
+            alglib.ap.assert(alglib.ap.len(xc)>=k, "Spline1DFitHermiteWC: Length(XC)<K!");
+            alglib.ap.assert(alglib.ap.len(yc)>=k, "Spline1DFitHermiteWC: Length(YC)<K!");
+            alglib.ap.assert(alglib.ap.len(dc)>=k, "Spline1DFitHermiteWC: Length(DC)<K!");
+            alglib.ap.assert(apserv.isfinitevector(x, n), "Spline1DFitHermiteWC: X contains infinite or NAN values!");
+            alglib.ap.assert(apserv.isfinitevector(y, n), "Spline1DFitHermiteWC: Y contains infinite or NAN values!");
+            alglib.ap.assert(apserv.isfinitevector(w, n), "Spline1DFitHermiteWC: Y contains infinite or NAN values!");
+            alglib.ap.assert(apserv.isfinitevector(xc, k), "Spline1DFitHermiteWC: X contains infinite or NAN values!");
+            alglib.ap.assert(apserv.isfinitevector(yc, k), "Spline1DFitHermiteWC: Y contains infinite or NAN values!");
+            for(i=0; i<=k-1; i++)
+            {
+                alglib.ap.assert(dc[i]==0 || dc[i]==1, "Spline1DFitHermiteWC: DC[i] is neither 0 or 1!");
+            }
+            spline1dfitinternal(1, x, y, w, n, xc, yc, dc, k, m, ref info, s, rep);
+        }
+
+
+        /*************************************************************************
+        Least squares fitting by cubic spline.
+
+        This subroutine is "lightweight" alternative for more complex and feature-
+        rich Spline1DFitCubicWC().  See  Spline1DFitCubicWC() for more information
+        about subroutine parameters (we don't duplicate it here because of length)
+
+          -- ALGLIB PROJECT --
+             Copyright 18.08.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spline1dfitcubic(double[] x,
+            double[] y,
+            int n,
+            int m,
+            ref int info,
+            spline1d.spline1dinterpolant s,
+            spline1dfitreport rep)
+        {
+            int i = 0;
+            double[] w = new double[0];
+            double[] xc = new double[0];
+            double[] yc = new double[0];
+            int[] dc = new int[0];
+
+            info = 0;
+
+            alglib.ap.assert(n>=1, "Spline1DFitCubic: N<1!");
+            alglib.ap.assert(m>=4, "Spline1DFitCubic: M<4!");
+            alglib.ap.assert(alglib.ap.len(x)>=n, "Spline1DFitCubic: Length(X)<N!");
+            alglib.ap.assert(alglib.ap.len(y)>=n, "Spline1DFitCubic: Length(Y)<N!");
+            alglib.ap.assert(apserv.isfinitevector(x, n), "Spline1DFitCubic: X contains infinite or NAN values!");
+            alglib.ap.assert(apserv.isfinitevector(y, n), "Spline1DFitCubic: Y contains infinite or NAN values!");
+            w = new double[n];
+            for(i=0; i<=n-1; i++)
+            {
+                w[i] = 1;
+            }
+            spline1dfitcubicwc(x, y, w, n, xc, yc, dc, 0, m, ref info, s, rep);
+        }
+
+
+        /*************************************************************************
+        Least squares fitting by Hermite spline.
+
+        This subroutine is "lightweight" alternative for more complex and feature-
+        rich Spline1DFitHermiteWC().  See Spline1DFitHermiteWC()  description  for
+        more information about subroutine parameters (we don't duplicate  it  here
+        because of length).
+
+          -- ALGLIB PROJECT --
+             Copyright 18.08.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spline1dfithermite(double[] x,
+            double[] y,
+            int n,
+            int m,
+            ref int info,
+            spline1d.spline1dinterpolant s,
+            spline1dfitreport rep)
+        {
+            int i = 0;
+            double[] w = new double[0];
+            double[] xc = new double[0];
+            double[] yc = new double[0];
+            int[] dc = new int[0];
+
+            info = 0;
+
+            alglib.ap.assert(n>=1, "Spline1DFitHermite: N<1!");
+            alglib.ap.assert(m>=4, "Spline1DFitHermite: M<4!");
+            alglib.ap.assert(m%2==0, "Spline1DFitHermite: M is odd!");
+            alglib.ap.assert(alglib.ap.len(x)>=n, "Spline1DFitHermite: Length(X)<N!");
+            alglib.ap.assert(alglib.ap.len(y)>=n, "Spline1DFitHermite: Length(Y)<N!");
+            alglib.ap.assert(apserv.isfinitevector(x, n), "Spline1DFitHermite: X contains infinite or NAN values!");
+            alglib.ap.assert(apserv.isfinitevector(y, n), "Spline1DFitHermite: Y contains infinite or NAN values!");
+            w = new double[n];
+            for(i=0; i<=n-1; i++)
+            {
+                w[i] = 1;
+            }
+            spline1dfithermitewc(x, y, w, n, xc, yc, dc, 0, m, ref info, s, rep);
+        }
+
+
+        /*************************************************************************
+        Weighted linear least squares fitting.
+
+        QR decomposition is used to reduce task to MxM, then triangular solver  or
+        SVD-based solver is used depending on condition number of the  system.  It
+        allows to maximize speed and retain decent accuracy.
+
+        INPUT PARAMETERS:
+            Y       -   array[0..N-1] Function values in  N  points.
+            W       -   array[0..N-1]  Weights  corresponding to function  values.
+                        Each summand in square  sum  of  approximation  deviations
+                        from  given  values  is  multiplied  by  the   square   of
+                        corresponding weight.
+            FMatrix -   a table of basis functions values, array[0..N-1, 0..M-1].
+                        FMatrix[I, J] - value of J-th basis function in I-th point.
+            N       -   number of points used. N>=1.
+            M       -   number of basis functions, M>=1.
+
+        OUTPUT PARAMETERS:
+            Info    -   error code:
+                        * -4    internal SVD decomposition subroutine failed (very
+                                rare and for degenerate systems only)
+                        * -1    incorrect N/M were specified
+                        *  1    task is solved
+            C       -   decomposition coefficients, array[0..M-1]
+            Rep     -   fitting report. Following fields are set:
+                        * Rep.TaskRCond     reciprocal of condition number
+                        * RMSError          rms error on the (X,Y).
+                        * AvgError          average error on the (X,Y).
+                        * AvgRelError       average relative error on the non-zero Y
+                        * MaxError          maximum error
+                                            NON-WEIGHTED ERRORS ARE CALCULATED
+
+          -- ALGLIB --
+             Copyright 17.08.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void lsfitlinearw(double[] y,
+            double[] w,
+            double[,] fmatrix,
+            int n,
+            int m,
+            ref int info,
+            ref double[] c,
+            lsfitreport rep)
+        {
+            info = 0;
+            c = new double[0];
+
+            alglib.ap.assert(n>=1, "LSFitLinearW: N<1!");
+            alglib.ap.assert(m>=1, "LSFitLinearW: M<1!");
+            alglib.ap.assert(alglib.ap.len(y)>=n, "LSFitLinearW: length(Y)<N!");
+            alglib.ap.assert(apserv.isfinitevector(y, n), "LSFitLinearW: Y contains infinite or NaN values!");
+            alglib.ap.assert(alglib.ap.len(w)>=n, "LSFitLinearW: length(W)<N!");
+            alglib.ap.assert(apserv.isfinitevector(w, n), "LSFitLinearW: W contains infinite or NaN values!");
+            alglib.ap.assert(alglib.ap.rows(fmatrix)>=n, "LSFitLinearW: rows(FMatrix)<N!");
+            alglib.ap.assert(alglib.ap.cols(fmatrix)>=m, "LSFitLinearW: cols(FMatrix)<M!");
+            alglib.ap.assert(apserv.apservisfinitematrix(fmatrix, n, m), "LSFitLinearW: FMatrix contains infinite or NaN values!");
+            lsfitlinearinternal(y, w, fmatrix, n, m, ref info, ref c, rep);
+        }
+
+
+        /*************************************************************************
+        Weighted constained linear least squares fitting.
+
+        This  is  variation  of LSFitLinearW(), which searchs for min|A*x=b| given
+        that  K  additional  constaints  C*x=bc are satisfied. It reduces original
+        task to modified one: min|B*y-d| WITHOUT constraints,  then LSFitLinearW()
+        is called.
+
+        INPUT PARAMETERS:
+            Y       -   array[0..N-1] Function values in  N  points.
+            W       -   array[0..N-1]  Weights  corresponding to function  values.
+                        Each summand in square  sum  of  approximation  deviations
+                        from  given  values  is  multiplied  by  the   square   of
+                        corresponding weight.
+            FMatrix -   a table of basis functions values, array[0..N-1, 0..M-1].
+                        FMatrix[I,J] - value of J-th basis function in I-th point.
+            CMatrix -   a table of constaints, array[0..K-1,0..M].
+                        I-th row of CMatrix corresponds to I-th linear constraint:
+                        CMatrix[I,0]*C[0] + ... + CMatrix[I,M-1]*C[M-1] = CMatrix[I,M]
+            N       -   number of points used. N>=1.
+            M       -   number of basis functions, M>=1.
+            K       -   number of constraints, 0 <= K < M
+                        K=0 corresponds to absence of constraints.
+
+        OUTPUT PARAMETERS:
+            Info    -   error code:
+                        * -4    internal SVD decomposition subroutine failed (very
+                                rare and for degenerate systems only)
+                        * -3    either   too   many  constraints  (M   or   more),
+                                degenerate  constraints   (some   constraints  are
+                                repetead twice) or inconsistent  constraints  were
+                                specified.
+                        *  1    task is solved
+            C       -   decomposition coefficients, array[0..M-1]
+            Rep     -   fitting report. Following fields are set:
+                        * RMSError          rms error on the (X,Y).
+                        * AvgError          average error on the (X,Y).
+                        * AvgRelError       average relative error on the non-zero Y
+                        * MaxError          maximum error
+                                            NON-WEIGHTED ERRORS ARE CALCULATED
+
+        IMPORTANT:
+            this subroitine doesn't calculate task's condition number for K<>0.
+
+          -- ALGLIB --
+             Copyright 07.09.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void lsfitlinearwc(double[] y,
+            double[] w,
+            double[,] fmatrix,
+            double[,] cmatrix,
+            int n,
+            int m,
+            int k,
+            ref int info,
+            ref double[] c,
+            lsfitreport rep)
+        {
+            int i = 0;
+            int j = 0;
+            double[] tau = new double[0];
+            double[,] q = new double[0,0];
+            double[,] f2 = new double[0,0];
+            double[] tmp = new double[0];
+            double[] c0 = new double[0];
+            double v = 0;
+            int i_ = 0;
+
+            y = (double[])y.Clone();
+            cmatrix = (double[,])cmatrix.Clone();
+            info = 0;
+            c = new double[0];
+
+            alglib.ap.assert(n>=1, "LSFitLinearWC: N<1!");
+            alglib.ap.assert(m>=1, "LSFitLinearWC: M<1!");
+            alglib.ap.assert(k>=0, "LSFitLinearWC: K<0!");
+            alglib.ap.assert(alglib.ap.len(y)>=n, "LSFitLinearWC: length(Y)<N!");
+            alglib.ap.assert(apserv.isfinitevector(y, n), "LSFitLinearWC: Y contains infinite or NaN values!");
+            alglib.ap.assert(alglib.ap.len(w)>=n, "LSFitLinearWC: length(W)<N!");
+            alglib.ap.assert(apserv.isfinitevector(w, n), "LSFitLinearWC: W contains infinite or NaN values!");
+            alglib.ap.assert(alglib.ap.rows(fmatrix)>=n, "LSFitLinearWC: rows(FMatrix)<N!");
+            alglib.ap.assert(alglib.ap.cols(fmatrix)>=m, "LSFitLinearWC: cols(FMatrix)<M!");
+            alglib.ap.assert(apserv.apservisfinitematrix(fmatrix, n, m), "LSFitLinearWC: FMatrix contains infinite or NaN values!");
+            alglib.ap.assert(alglib.ap.rows(cmatrix)>=k, "LSFitLinearWC: rows(CMatrix)<K!");
+            alglib.ap.assert(alglib.ap.cols(cmatrix)>=m+1 || k==0, "LSFitLinearWC: cols(CMatrix)<M+1!");
+            alglib.ap.assert(apserv.apservisfinitematrix(cmatrix, k, m+1), "LSFitLinearWC: CMatrix contains infinite or NaN values!");
+            if( k>=m )
+            {
+                info = -3;
+                return;
+            }
+            
+            //
+            // Solve
+            //
+            if( k==0 )
+            {
+                
+                //
+                // no constraints
+                //
+                lsfitlinearinternal(y, w, fmatrix, n, m, ref info, ref c, rep);
+            }
+            else
+            {
+                
+                //
+                // First, find general form solution of constraints system:
+                // * factorize C = L*Q
+                // * unpack Q
+                // * fill upper part of C with zeros (for RCond)
+                //
+                // We got C=C0+Q2'*y where Q2 is lower M-K rows of Q.
+                //
+                ortfac.rmatrixlq(ref cmatrix, k, m, ref tau);
+                ortfac.rmatrixlqunpackq(cmatrix, k, m, tau, m, ref q);
+                for(i=0; i<=k-1; i++)
+                {
+                    for(j=i+1; j<=m-1; j++)
+                    {
+                        cmatrix[i,j] = 0.0;
+                    }
+                }
+                if( (double)(rcond.rmatrixlurcondinf(cmatrix, k))<(double)(1000*math.machineepsilon) )
+                {
+                    info = -3;
+                    return;
+                }
+                tmp = new double[k];
+                for(i=0; i<=k-1; i++)
+                {
+                    if( i>0 )
+                    {
+                        v = 0.0;
+                        for(i_=0; i_<=i-1;i_++)
+                        {
+                            v += cmatrix[i,i_]*tmp[i_];
+                        }
+                    }
+                    else
+                    {
+                        v = 0;
+                    }
+                    tmp[i] = (cmatrix[i,m]-v)/cmatrix[i,i];
+                }
+                c0 = new double[m];
+                for(i=0; i<=m-1; i++)
+                {
+                    c0[i] = 0;
+                }
+                for(i=0; i<=k-1; i++)
+                {
+                    v = tmp[i];
+                    for(i_=0; i_<=m-1;i_++)
+                    {
+                        c0[i_] = c0[i_] + v*q[i,i_];
+                    }
+                }
+                
+                //
+                // Second, prepare modified matrix F2 = F*Q2' and solve modified task
+                //
+                tmp = new double[Math.Max(n, m)+1];
+                f2 = new double[n, m-k];
+                blas.matrixvectormultiply(fmatrix, 0, n-1, 0, m-1, false, c0, 0, m-1, -1.0, ref y, 0, n-1, 1.0);
+                blas.matrixmatrixmultiply(fmatrix, 0, n-1, 0, m-1, false, q, k, m-1, 0, m-1, true, 1.0, ref f2, 0, n-1, 0, m-k-1, 0.0, ref tmp);
+                lsfitlinearinternal(y, w, f2, n, m-k, ref info, ref tmp, rep);
+                rep.taskrcond = -1;
+                if( info<=0 )
+                {
+                    return;
+                }
+                
+                //
+                // then, convert back to original answer: C = C0 + Q2'*Y0
+                //
+                c = new double[m];
+                for(i_=0; i_<=m-1;i_++)
+                {
+                    c[i_] = c0[i_];
+                }
+                blas.matrixvectormultiply(q, k, m-1, 0, m-1, true, tmp, 0, m-k-1, 1.0, ref c, 0, m-1, 1.0);
+            }
+        }
+
+
+        /*************************************************************************
+        Linear least squares fitting.
+
+        QR decomposition is used to reduce task to MxM, then triangular solver  or
+        SVD-based solver is used depending on condition number of the  system.  It
+        allows to maximize speed and retain decent accuracy.
+
+        INPUT PARAMETERS:
+            Y       -   array[0..N-1] Function values in  N  points.
+            FMatrix -   a table of basis functions values, array[0..N-1, 0..M-1].
+                        FMatrix[I, J] - value of J-th basis function in I-th point.
+            N       -   number of points used. N>=1.
+            M       -   number of basis functions, M>=1.
+
+        OUTPUT PARAMETERS:
+            Info    -   error code:
+                        * -4    internal SVD decomposition subroutine failed (very
+                                rare and for degenerate systems only)
+                        *  1    task is solved
+            C       -   decomposition coefficients, array[0..M-1]
+            Rep     -   fitting report. Following fields are set:
+                        * Rep.TaskRCond     reciprocal of condition number
+                        * RMSError          rms error on the (X,Y).
+                        * AvgError          average error on the (X,Y).
+                        * AvgRelError       average relative error on the non-zero Y
+                        * MaxError          maximum error
+                                            NON-WEIGHTED ERRORS ARE CALCULATED
+
+          -- ALGLIB --
+             Copyright 17.08.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void lsfitlinear(double[] y,
+            double[,] fmatrix,
+            int n,
+            int m,
+            ref int info,
+            ref double[] c,
+            lsfitreport rep)
+        {
+            double[] w = new double[0];
+            int i = 0;
+
+            info = 0;
+            c = new double[0];
+
+            alglib.ap.assert(n>=1, "LSFitLinear: N<1!");
+            alglib.ap.assert(m>=1, "LSFitLinear: M<1!");
+            alglib.ap.assert(alglib.ap.len(y)>=n, "LSFitLinear: length(Y)<N!");
+            alglib.ap.assert(apserv.isfinitevector(y, n), "LSFitLinear: Y contains infinite or NaN values!");
+            alglib.ap.assert(alglib.ap.rows(fmatrix)>=n, "LSFitLinear: rows(FMatrix)<N!");
+            alglib.ap.assert(alglib.ap.cols(fmatrix)>=m, "LSFitLinear: cols(FMatrix)<M!");
+            alglib.ap.assert(apserv.apservisfinitematrix(fmatrix, n, m), "LSFitLinear: FMatrix contains infinite or NaN values!");
+            w = new double[n];
+            for(i=0; i<=n-1; i++)
+            {
+                w[i] = 1;
+            }
+            lsfitlinearinternal(y, w, fmatrix, n, m, ref info, ref c, rep);
+        }
+
+
+        /*************************************************************************
+        Constained linear least squares fitting.
+
+        This  is  variation  of LSFitLinear(),  which searchs for min|A*x=b| given
+        that  K  additional  constaints  C*x=bc are satisfied. It reduces original
+        task to modified one: min|B*y-d| WITHOUT constraints,  then  LSFitLinear()
+        is called.
+
+        INPUT PARAMETERS:
+            Y       -   array[0..N-1] Function values in  N  points.
+            FMatrix -   a table of basis functions values, array[0..N-1, 0..M-1].
+                        FMatrix[I,J] - value of J-th basis function in I-th point.
+            CMatrix -   a table of constaints, array[0..K-1,0..M].
+                        I-th row of CMatrix corresponds to I-th linear constraint:
+                        CMatrix[I,0]*C[0] + ... + CMatrix[I,M-1]*C[M-1] = CMatrix[I,M]
+            N       -   number of points used. N>=1.
+            M       -   number of basis functions, M>=1.
+            K       -   number of constraints, 0 <= K < M
+                        K=0 corresponds to absence of constraints.
+
+        OUTPUT PARAMETERS:
+            Info    -   error code:
+                        * -4    internal SVD decomposition subroutine failed (very
+                                rare and for degenerate systems only)
+                        * -3    either   too   many  constraints  (M   or   more),
+                                degenerate  constraints   (some   constraints  are
+                                repetead twice) or inconsistent  constraints  were
+                                specified.
+                        *  1    task is solved
+            C       -   decomposition coefficients, array[0..M-1]
+            Rep     -   fitting report. Following fields are set:
+                        * RMSError          rms error on the (X,Y).
+                        * AvgError          average error on the (X,Y).
+                        * AvgRelError       average relative error on the non-zero Y
+                        * MaxError          maximum error
+                                            NON-WEIGHTED ERRORS ARE CALCULATED
+
+        IMPORTANT:
+            this subroitine doesn't calculate task's condition number for K<>0.
+
+          -- ALGLIB --
+             Copyright 07.09.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void lsfitlinearc(double[] y,
+            double[,] fmatrix,
+            double[,] cmatrix,
+            int n,
+            int m,
+            int k,
+            ref int info,
+            ref double[] c,
+            lsfitreport rep)
+        {
+            double[] w = new double[0];
+            int i = 0;
+
+            y = (double[])y.Clone();
+            info = 0;
+            c = new double[0];
+
+            alglib.ap.assert(n>=1, "LSFitLinearC: N<1!");
+            alglib.ap.assert(m>=1, "LSFitLinearC: M<1!");
+            alglib.ap.assert(k>=0, "LSFitLinearC: K<0!");
+            alglib.ap.assert(alglib.ap.len(y)>=n, "LSFitLinearC: length(Y)<N!");
+            alglib.ap.assert(apserv.isfinitevector(y, n), "LSFitLinearC: Y contains infinite or NaN values!");
+            alglib.ap.assert(alglib.ap.rows(fmatrix)>=n, "LSFitLinearC: rows(FMatrix)<N!");
+            alglib.ap.assert(alglib.ap.cols(fmatrix)>=m, "LSFitLinearC: cols(FMatrix)<M!");
+            alglib.ap.assert(apserv.apservisfinitematrix(fmatrix, n, m), "LSFitLinearC: FMatrix contains infinite or NaN values!");
+            alglib.ap.assert(alglib.ap.rows(cmatrix)>=k, "LSFitLinearC: rows(CMatrix)<K!");
+            alglib.ap.assert(alglib.ap.cols(cmatrix)>=m+1 || k==0, "LSFitLinearC: cols(CMatrix)<M+1!");
+            alglib.ap.assert(apserv.apservisfinitematrix(cmatrix, k, m+1), "LSFitLinearC: CMatrix contains infinite or NaN values!");
+            w = new double[n];
+            for(i=0; i<=n-1; i++)
+            {
+                w[i] = 1;
+            }
+            lsfitlinearwc(y, w, fmatrix, cmatrix, n, m, k, ref info, ref c, rep);
+        }
+
+
+        /*************************************************************************
+        Weighted nonlinear least squares fitting using function values only.
+
+        Combination of numerical differentiation and secant updates is used to
+        obtain function Jacobian.
+
+        Nonlinear task min(F(c)) is solved, where
+
+            F(c) = (w[0]*(f(c,x[0])-y[0]))^2 + ... + (w[n-1]*(f(c,x[n-1])-y[n-1]))^2,
+
+            * N is a number of points,
+            * M is a dimension of a space points belong to,
+            * K is a dimension of a space of parameters being fitted,
+            * w is an N-dimensional vector of weight coefficients,
+            * x is a set of N points, each of them is an M-dimensional vector,
+            * c is a K-dimensional vector of parameters being fitted
+
+        This subroutine uses only f(c,x[i]).
+
+        INPUT PARAMETERS:
+            X       -   array[0..N-1,0..M-1], points (one row = one point)
+            Y       -   array[0..N-1], function values.
+            W       -   weights, array[0..N-1]
+            C       -   array[0..K-1], initial approximation to the solution,
+            N       -   number of points, N>1
+            M       -   dimension of space
+            K       -   number of parameters being fitted
+            DiffStep-   numerical differentiation step;
+                        should not be very small or large;
+                        large = loss of accuracy
+                        small = growth of round-off errors
+
+        OUTPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+
+          -- ALGLIB --
+             Copyright 18.10.2008 by Bochkanov Sergey
+        *************************************************************************/
+        public static void lsfitcreatewf(double[,] x,
+            double[] y,
+            double[] w,
+            double[] c,
+            int n,
+            int m,
+            int k,
+            double diffstep,
+            lsfitstate state)
+        {
+            int i = 0;
+            int i_ = 0;
+
+            alglib.ap.assert(n>=1, "LSFitCreateWF: N<1!");
+            alglib.ap.assert(m>=1, "LSFitCreateWF: M<1!");
+            alglib.ap.assert(k>=1, "LSFitCreateWF: K<1!");
+            alglib.ap.assert(alglib.ap.len(c)>=k, "LSFitCreateWF: length(C)<K!");
+            alglib.ap.assert(apserv.isfinitevector(c, k), "LSFitCreateWF: C contains infinite or NaN values!");
+            alglib.ap.assert(alglib.ap.len(y)>=n, "LSFitCreateWF: length(Y)<N!");
+            alglib.ap.assert(apserv.isfinitevector(y, n), "LSFitCreateWF: Y contains infinite or NaN values!");
+            alglib.ap.assert(alglib.ap.len(w)>=n, "LSFitCreateWF: length(W)<N!");
+            alglib.ap.assert(apserv.isfinitevector(w, n), "LSFitCreateWF: W contains infinite or NaN values!");
+            alglib.ap.assert(alglib.ap.rows(x)>=n, "LSFitCreateWF: rows(X)<N!");
+            alglib.ap.assert(alglib.ap.cols(x)>=m, "LSFitCreateWF: cols(X)<M!");
+            alglib.ap.assert(apserv.apservisfinitematrix(x, n, m), "LSFitCreateWF: X contains infinite or NaN values!");
+            alglib.ap.assert(math.isfinite(diffstep), "LSFitCreateWF: DiffStep is not finite!");
+            alglib.ap.assert((double)(diffstep)>(double)(0), "LSFitCreateWF: DiffStep<=0!");
+            state.npoints = n;
+            state.nweights = n;
+            state.wkind = 1;
+            state.m = m;
+            state.k = k;
+            lsfitsetcond(state, 0.0, 0.0, 0);
+            lsfitsetstpmax(state, 0.0);
+            lsfitsetxrep(state, false);
+            state.taskx = new double[n, m];
+            state.tasky = new double[n];
+            state.w = new double[n];
+            state.c = new double[k];
+            state.x = new double[m];
+            for(i_=0; i_<=k-1;i_++)
+            {
+                state.c[i_] = c[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.w[i_] = w[i_];
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                for(i_=0; i_<=m-1;i_++)
+                {
+                    state.taskx[i,i_] = x[i,i_];
+                }
+                state.tasky[i] = y[i];
+            }
+            state.s = new double[k];
+            state.bndl = new double[k];
+            state.bndu = new double[k];
+            for(i=0; i<=k-1; i++)
+            {
+                state.s[i] = 1.0;
+                state.bndl[i] = Double.NegativeInfinity;
+                state.bndu[i] = Double.PositiveInfinity;
+            }
+            state.optalgo = 0;
+            state.prevnpt = -1;
+            state.prevalgo = -1;
+            minlm.minlmcreatev(k, n, state.c, diffstep, state.optstate);
+            lsfitclearrequestfields(state);
+            state.rstate.ia = new int[4+1];
+            state.rstate.ra = new double[2+1];
+            state.rstate.stage = -1;
+        }
+
+
+        /*************************************************************************
+        Nonlinear least squares fitting using function values only.
+
+        Combination of numerical differentiation and secant updates is used to
+        obtain function Jacobian.
+
+        Nonlinear task min(F(c)) is solved, where
+
+            F(c) = (f(c,x[0])-y[0])^2 + ... + (f(c,x[n-1])-y[n-1])^2,
+
+            * N is a number of points,
+            * M is a dimension of a space points belong to,
+            * K is a dimension of a space of parameters being fitted,
+            * w is an N-dimensional vector of weight coefficients,
+            * x is a set of N points, each of them is an M-dimensional vector,
+            * c is a K-dimensional vector of parameters being fitted
+
+        This subroutine uses only f(c,x[i]).
+
+        INPUT PARAMETERS:
+            X       -   array[0..N-1,0..M-1], points (one row = one point)
+            Y       -   array[0..N-1], function values.
+            C       -   array[0..K-1], initial approximation to the solution,
+            N       -   number of points, N>1
+            M       -   dimension of space
+            K       -   number of parameters being fitted
+            DiffStep-   numerical differentiation step;
+                        should not be very small or large;
+                        large = loss of accuracy
+                        small = growth of round-off errors
+
+        OUTPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+
+          -- ALGLIB --
+             Copyright 18.10.2008 by Bochkanov Sergey
+        *************************************************************************/
+        public static void lsfitcreatef(double[,] x,
+            double[] y,
+            double[] c,
+            int n,
+            int m,
+            int k,
+            double diffstep,
+            lsfitstate state)
+        {
+            int i = 0;
+            int i_ = 0;
+
+            alglib.ap.assert(n>=1, "LSFitCreateF: N<1!");
+            alglib.ap.assert(m>=1, "LSFitCreateF: M<1!");
+            alglib.ap.assert(k>=1, "LSFitCreateF: K<1!");
+            alglib.ap.assert(alglib.ap.len(c)>=k, "LSFitCreateF: length(C)<K!");
+            alglib.ap.assert(apserv.isfinitevector(c, k), "LSFitCreateF: C contains infinite or NaN values!");
+            alglib.ap.assert(alglib.ap.len(y)>=n, "LSFitCreateF: length(Y)<N!");
+            alglib.ap.assert(apserv.isfinitevector(y, n), "LSFitCreateF: Y contains infinite or NaN values!");
+            alglib.ap.assert(alglib.ap.rows(x)>=n, "LSFitCreateF: rows(X)<N!");
+            alglib.ap.assert(alglib.ap.cols(x)>=m, "LSFitCreateF: cols(X)<M!");
+            alglib.ap.assert(apserv.apservisfinitematrix(x, n, m), "LSFitCreateF: X contains infinite or NaN values!");
+            alglib.ap.assert(alglib.ap.rows(x)>=n, "LSFitCreateF: rows(X)<N!");
+            alglib.ap.assert(alglib.ap.cols(x)>=m, "LSFitCreateF: cols(X)<M!");
+            alglib.ap.assert(apserv.apservisfinitematrix(x, n, m), "LSFitCreateF: X contains infinite or NaN values!");
+            alglib.ap.assert(math.isfinite(diffstep), "LSFitCreateF: DiffStep is not finite!");
+            alglib.ap.assert((double)(diffstep)>(double)(0), "LSFitCreateF: DiffStep<=0!");
+            state.npoints = n;
+            state.wkind = 0;
+            state.m = m;
+            state.k = k;
+            lsfitsetcond(state, 0.0, 0.0, 0);
+            lsfitsetstpmax(state, 0.0);
+            lsfitsetxrep(state, false);
+            state.taskx = new double[n, m];
+            state.tasky = new double[n];
+            state.c = new double[k];
+            state.x = new double[m];
+            for(i_=0; i_<=k-1;i_++)
+            {
+                state.c[i_] = c[i_];
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                for(i_=0; i_<=m-1;i_++)
+                {
+                    state.taskx[i,i_] = x[i,i_];
+                }
+                state.tasky[i] = y[i];
+            }
+            state.s = new double[k];
+            state.bndl = new double[k];
+            state.bndu = new double[k];
+            for(i=0; i<=k-1; i++)
+            {
+                state.s[i] = 1.0;
+                state.bndl[i] = Double.NegativeInfinity;
+                state.bndu[i] = Double.PositiveInfinity;
+            }
+            state.optalgo = 0;
+            state.prevnpt = -1;
+            state.prevalgo = -1;
+            minlm.minlmcreatev(k, n, state.c, diffstep, state.optstate);
+            lsfitclearrequestfields(state);
+            state.rstate.ia = new int[4+1];
+            state.rstate.ra = new double[2+1];
+            state.rstate.stage = -1;
+        }
+
+
+        /*************************************************************************
+        Weighted nonlinear least squares fitting using gradient only.
+
+        Nonlinear task min(F(c)) is solved, where
+
+            F(c) = (w[0]*(f(c,x[0])-y[0]))^2 + ... + (w[n-1]*(f(c,x[n-1])-y[n-1]))^2,
+            
+            * N is a number of points,
+            * M is a dimension of a space points belong to,
+            * K is a dimension of a space of parameters being fitted,
+            * w is an N-dimensional vector of weight coefficients,
+            * x is a set of N points, each of them is an M-dimensional vector,
+            * c is a K-dimensional vector of parameters being fitted
+            
+        This subroutine uses only f(c,x[i]) and its gradient.
+            
+        INPUT PARAMETERS:
+            X       -   array[0..N-1,0..M-1], points (one row = one point)
+            Y       -   array[0..N-1], function values.
+            W       -   weights, array[0..N-1]
+            C       -   array[0..K-1], initial approximation to the solution,
+            N       -   number of points, N>1
+            M       -   dimension of space
+            K       -   number of parameters being fitted
+            CheapFG -   boolean flag, which is:
+                        * True  if both function and gradient calculation complexity
+                                are less than O(M^2).  An improved  algorithm  can
+                                be  used  which corresponds  to  FGJ  scheme  from
+                                MINLM unit.
+                        * False otherwise.
+                                Standard Jacibian-bases  Levenberg-Marquardt  algo
+                                will be used (FJ scheme).
+
+        OUTPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+
+        See also:
+            LSFitResults
+            LSFitCreateFG (fitting without weights)
+            LSFitCreateWFGH (fitting using Hessian)
+            LSFitCreateFGH (fitting using Hessian, without weights)
+
+          -- ALGLIB --
+             Copyright 17.08.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void lsfitcreatewfg(double[,] x,
+            double[] y,
+            double[] w,
+            double[] c,
+            int n,
+            int m,
+            int k,
+            bool cheapfg,
+            lsfitstate state)
+        {
+            int i = 0;
+            int i_ = 0;
+
+            alglib.ap.assert(n>=1, "LSFitCreateWFG: N<1!");
+            alglib.ap.assert(m>=1, "LSFitCreateWFG: M<1!");
+            alglib.ap.assert(k>=1, "LSFitCreateWFG: K<1!");
+            alglib.ap.assert(alglib.ap.len(c)>=k, "LSFitCreateWFG: length(C)<K!");
+            alglib.ap.assert(apserv.isfinitevector(c, k), "LSFitCreateWFG: C contains infinite or NaN values!");
+            alglib.ap.assert(alglib.ap.len(y)>=n, "LSFitCreateWFG: length(Y)<N!");
+            alglib.ap.assert(apserv.isfinitevector(y, n), "LSFitCreateWFG: Y contains infinite or NaN values!");
+            alglib.ap.assert(alglib.ap.len(w)>=n, "LSFitCreateWFG: length(W)<N!");
+            alglib.ap.assert(apserv.isfinitevector(w, n), "LSFitCreateWFG: W contains infinite or NaN values!");
+            alglib.ap.assert(alglib.ap.rows(x)>=n, "LSFitCreateWFG: rows(X)<N!");
+            alglib.ap.assert(alglib.ap.cols(x)>=m, "LSFitCreateWFG: cols(X)<M!");
+            alglib.ap.assert(apserv.apservisfinitematrix(x, n, m), "LSFitCreateWFG: X contains infinite or NaN values!");
+            state.npoints = n;
+            state.nweights = n;
+            state.wkind = 1;
+            state.m = m;
+            state.k = k;
+            lsfitsetcond(state, 0.0, 0.0, 0);
+            lsfitsetstpmax(state, 0.0);
+            lsfitsetxrep(state, false);
+            state.taskx = new double[n, m];
+            state.tasky = new double[n];
+            state.w = new double[n];
+            state.c = new double[k];
+            state.x = new double[m];
+            state.g = new double[k];
+            for(i_=0; i_<=k-1;i_++)
+            {
+                state.c[i_] = c[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.w[i_] = w[i_];
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                for(i_=0; i_<=m-1;i_++)
+                {
+                    state.taskx[i,i_] = x[i,i_];
+                }
+                state.tasky[i] = y[i];
+            }
+            state.s = new double[k];
+            state.bndl = new double[k];
+            state.bndu = new double[k];
+            for(i=0; i<=k-1; i++)
+            {
+                state.s[i] = 1.0;
+                state.bndl[i] = Double.NegativeInfinity;
+                state.bndu[i] = Double.PositiveInfinity;
+            }
+            state.optalgo = 1;
+            state.prevnpt = -1;
+            state.prevalgo = -1;
+            if( cheapfg )
+            {
+                minlm.minlmcreatevgj(k, n, state.c, state.optstate);
+            }
+            else
+            {
+                minlm.minlmcreatevj(k, n, state.c, state.optstate);
+            }
+            lsfitclearrequestfields(state);
+            state.rstate.ia = new int[4+1];
+            state.rstate.ra = new double[2+1];
+            state.rstate.stage = -1;
+        }
+
+
+        /*************************************************************************
+        Nonlinear least squares fitting using gradient only, without individual
+        weights.
+
+        Nonlinear task min(F(c)) is solved, where
+
+            F(c) = ((f(c,x[0])-y[0]))^2 + ... + ((f(c,x[n-1])-y[n-1]))^2,
+
+            * N is a number of points,
+            * M is a dimension of a space points belong to,
+            * K is a dimension of a space of parameters being fitted,
+            * x is a set of N points, each of them is an M-dimensional vector,
+            * c is a K-dimensional vector of parameters being fitted
+
+        This subroutine uses only f(c,x[i]) and its gradient.
+
+        INPUT PARAMETERS:
+            X       -   array[0..N-1,0..M-1], points (one row = one point)
+            Y       -   array[0..N-1], function values.
+            C       -   array[0..K-1], initial approximation to the solution,
+            N       -   number of points, N>1
+            M       -   dimension of space
+            K       -   number of parameters being fitted
+            CheapFG -   boolean flag, which is:
+                        * True  if both function and gradient calculation complexity
+                                are less than O(M^2).  An improved  algorithm  can
+                                be  used  which corresponds  to  FGJ  scheme  from
+                                MINLM unit.
+                        * False otherwise.
+                                Standard Jacibian-bases  Levenberg-Marquardt  algo
+                                will be used (FJ scheme).
+
+        OUTPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+
+          -- ALGLIB --
+             Copyright 17.08.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void lsfitcreatefg(double[,] x,
+            double[] y,
+            double[] c,
+            int n,
+            int m,
+            int k,
+            bool cheapfg,
+            lsfitstate state)
+        {
+            int i = 0;
+            int i_ = 0;
+
+            alglib.ap.assert(n>=1, "LSFitCreateFG: N<1!");
+            alglib.ap.assert(m>=1, "LSFitCreateFG: M<1!");
+            alglib.ap.assert(k>=1, "LSFitCreateFG: K<1!");
+            alglib.ap.assert(alglib.ap.len(c)>=k, "LSFitCreateFG: length(C)<K!");
+            alglib.ap.assert(apserv.isfinitevector(c, k), "LSFitCreateFG: C contains infinite or NaN values!");
+            alglib.ap.assert(alglib.ap.len(y)>=n, "LSFitCreateFG: length(Y)<N!");
+            alglib.ap.assert(apserv.isfinitevector(y, n), "LSFitCreateFG: Y contains infinite or NaN values!");
+            alglib.ap.assert(alglib.ap.rows(x)>=n, "LSFitCreateFG: rows(X)<N!");
+            alglib.ap.assert(alglib.ap.cols(x)>=m, "LSFitCreateFG: cols(X)<M!");
+            alglib.ap.assert(apserv.apservisfinitematrix(x, n, m), "LSFitCreateFG: X contains infinite or NaN values!");
+            alglib.ap.assert(alglib.ap.rows(x)>=n, "LSFitCreateFG: rows(X)<N!");
+            alglib.ap.assert(alglib.ap.cols(x)>=m, "LSFitCreateFG: cols(X)<M!");
+            alglib.ap.assert(apserv.apservisfinitematrix(x, n, m), "LSFitCreateFG: X contains infinite or NaN values!");
+            state.npoints = n;
+            state.wkind = 0;
+            state.m = m;
+            state.k = k;
+            lsfitsetcond(state, 0.0, 0.0, 0);
+            lsfitsetstpmax(state, 0.0);
+            lsfitsetxrep(state, false);
+            state.taskx = new double[n, m];
+            state.tasky = new double[n];
+            state.c = new double[k];
+            state.x = new double[m];
+            state.g = new double[k];
+            for(i_=0; i_<=k-1;i_++)
+            {
+                state.c[i_] = c[i_];
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                for(i_=0; i_<=m-1;i_++)
+                {
+                    state.taskx[i,i_] = x[i,i_];
+                }
+                state.tasky[i] = y[i];
+            }
+            state.s = new double[k];
+            state.bndl = new double[k];
+            state.bndu = new double[k];
+            for(i=0; i<=k-1; i++)
+            {
+                state.s[i] = 1.0;
+                state.bndl[i] = Double.NegativeInfinity;
+                state.bndu[i] = Double.PositiveInfinity;
+            }
+            state.optalgo = 1;
+            state.prevnpt = -1;
+            state.prevalgo = -1;
+            if( cheapfg )
+            {
+                minlm.minlmcreatevgj(k, n, state.c, state.optstate);
+            }
+            else
+            {
+                minlm.minlmcreatevj(k, n, state.c, state.optstate);
+            }
+            lsfitclearrequestfields(state);
+            state.rstate.ia = new int[4+1];
+            state.rstate.ra = new double[2+1];
+            state.rstate.stage = -1;
+        }
+
+
+        /*************************************************************************
+        Weighted nonlinear least squares fitting using gradient/Hessian.
+
+        Nonlinear task min(F(c)) is solved, where
+
+            F(c) = (w[0]*(f(c,x[0])-y[0]))^2 + ... + (w[n-1]*(f(c,x[n-1])-y[n-1]))^2,
+
+            * N is a number of points,
+            * M is a dimension of a space points belong to,
+            * K is a dimension of a space of parameters being fitted,
+            * w is an N-dimensional vector of weight coefficients,
+            * x is a set of N points, each of them is an M-dimensional vector,
+            * c is a K-dimensional vector of parameters being fitted
+
+        This subroutine uses f(c,x[i]), its gradient and its Hessian.
+
+        INPUT PARAMETERS:
+            X       -   array[0..N-1,0..M-1], points (one row = one point)
+            Y       -   array[0..N-1], function values.
+            W       -   weights, array[0..N-1]
+            C       -   array[0..K-1], initial approximation to the solution,
+            N       -   number of points, N>1
+            M       -   dimension of space
+            K       -   number of parameters being fitted
+
+        OUTPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+
+          -- ALGLIB --
+             Copyright 17.08.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void lsfitcreatewfgh(double[,] x,
+            double[] y,
+            double[] w,
+            double[] c,
+            int n,
+            int m,
+            int k,
+            lsfitstate state)
+        {
+            int i = 0;
+            int i_ = 0;
+
+            alglib.ap.assert(n>=1, "LSFitCreateWFGH: N<1!");
+            alglib.ap.assert(m>=1, "LSFitCreateWFGH: M<1!");
+            alglib.ap.assert(k>=1, "LSFitCreateWFGH: K<1!");
+            alglib.ap.assert(alglib.ap.len(c)>=k, "LSFitCreateWFGH: length(C)<K!");
+            alglib.ap.assert(apserv.isfinitevector(c, k), "LSFitCreateWFGH: C contains infinite or NaN values!");
+            alglib.ap.assert(alglib.ap.len(y)>=n, "LSFitCreateWFGH: length(Y)<N!");
+            alglib.ap.assert(apserv.isfinitevector(y, n), "LSFitCreateWFGH: Y contains infinite or NaN values!");
+            alglib.ap.assert(alglib.ap.len(w)>=n, "LSFitCreateWFGH: length(W)<N!");
+            alglib.ap.assert(apserv.isfinitevector(w, n), "LSFitCreateWFGH: W contains infinite or NaN values!");
+            alglib.ap.assert(alglib.ap.rows(x)>=n, "LSFitCreateWFGH: rows(X)<N!");
+            alglib.ap.assert(alglib.ap.cols(x)>=m, "LSFitCreateWFGH: cols(X)<M!");
+            alglib.ap.assert(apserv.apservisfinitematrix(x, n, m), "LSFitCreateWFGH: X contains infinite or NaN values!");
+            state.npoints = n;
+            state.nweights = n;
+            state.wkind = 1;
+            state.m = m;
+            state.k = k;
+            lsfitsetcond(state, 0.0, 0.0, 0);
+            lsfitsetstpmax(state, 0.0);
+            lsfitsetxrep(state, false);
+            state.taskx = new double[n, m];
+            state.tasky = new double[n];
+            state.w = new double[n];
+            state.c = new double[k];
+            state.h = new double[k, k];
+            state.x = new double[m];
+            state.g = new double[k];
+            for(i_=0; i_<=k-1;i_++)
+            {
+                state.c[i_] = c[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.w[i_] = w[i_];
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                for(i_=0; i_<=m-1;i_++)
+                {
+                    state.taskx[i,i_] = x[i,i_];
+                }
+                state.tasky[i] = y[i];
+            }
+            state.s = new double[k];
+            state.bndl = new double[k];
+            state.bndu = new double[k];
+            for(i=0; i<=k-1; i++)
+            {
+                state.s[i] = 1.0;
+                state.bndl[i] = Double.NegativeInfinity;
+                state.bndu[i] = Double.PositiveInfinity;
+            }
+            state.optalgo = 2;
+            state.prevnpt = -1;
+            state.prevalgo = -1;
+            minlm.minlmcreatefgh(k, state.c, state.optstate);
+            lsfitclearrequestfields(state);
+            state.rstate.ia = new int[4+1];
+            state.rstate.ra = new double[2+1];
+            state.rstate.stage = -1;
+        }
+
+
+        /*************************************************************************
+        Nonlinear least squares fitting using gradient/Hessian, without individial
+        weights.
+
+        Nonlinear task min(F(c)) is solved, where
+
+            F(c) = ((f(c,x[0])-y[0]))^2 + ... + ((f(c,x[n-1])-y[n-1]))^2,
+
+            * N is a number of points,
+            * M is a dimension of a space points belong to,
+            * K is a dimension of a space of parameters being fitted,
+            * x is a set of N points, each of them is an M-dimensional vector,
+            * c is a K-dimensional vector of parameters being fitted
+
+        This subroutine uses f(c,x[i]), its gradient and its Hessian.
+
+        INPUT PARAMETERS:
+            X       -   array[0..N-1,0..M-1], points (one row = one point)
+            Y       -   array[0..N-1], function values.
+            C       -   array[0..K-1], initial approximation to the solution,
+            N       -   number of points, N>1
+            M       -   dimension of space
+            K       -   number of parameters being fitted
+
+        OUTPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+
+
+          -- ALGLIB --
+             Copyright 17.08.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void lsfitcreatefgh(double[,] x,
+            double[] y,
+            double[] c,
+            int n,
+            int m,
+            int k,
+            lsfitstate state)
+        {
+            int i = 0;
+            int i_ = 0;
+
+            alglib.ap.assert(n>=1, "LSFitCreateFGH: N<1!");
+            alglib.ap.assert(m>=1, "LSFitCreateFGH: M<1!");
+            alglib.ap.assert(k>=1, "LSFitCreateFGH: K<1!");
+            alglib.ap.assert(alglib.ap.len(c)>=k, "LSFitCreateFGH: length(C)<K!");
+            alglib.ap.assert(apserv.isfinitevector(c, k), "LSFitCreateFGH: C contains infinite or NaN values!");
+            alglib.ap.assert(alglib.ap.len(y)>=n, "LSFitCreateFGH: length(Y)<N!");
+            alglib.ap.assert(apserv.isfinitevector(y, n), "LSFitCreateFGH: Y contains infinite or NaN values!");
+            alglib.ap.assert(alglib.ap.rows(x)>=n, "LSFitCreateFGH: rows(X)<N!");
+            alglib.ap.assert(alglib.ap.cols(x)>=m, "LSFitCreateFGH: cols(X)<M!");
+            alglib.ap.assert(apserv.apservisfinitematrix(x, n, m), "LSFitCreateFGH: X contains infinite or NaN values!");
+            state.npoints = n;
+            state.wkind = 0;
+            state.m = m;
+            state.k = k;
+            lsfitsetcond(state, 0.0, 0.0, 0);
+            lsfitsetstpmax(state, 0.0);
+            lsfitsetxrep(state, false);
+            state.taskx = new double[n, m];
+            state.tasky = new double[n];
+            state.c = new double[k];
+            state.h = new double[k, k];
+            state.x = new double[m];
+            state.g = new double[k];
+            for(i_=0; i_<=k-1;i_++)
+            {
+                state.c[i_] = c[i_];
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                for(i_=0; i_<=m-1;i_++)
+                {
+                    state.taskx[i,i_] = x[i,i_];
+                }
+                state.tasky[i] = y[i];
+            }
+            state.s = new double[k];
+            state.bndl = new double[k];
+            state.bndu = new double[k];
+            for(i=0; i<=k-1; i++)
+            {
+                state.s[i] = 1.0;
+                state.bndl[i] = Double.NegativeInfinity;
+                state.bndu[i] = Double.PositiveInfinity;
+            }
+            state.optalgo = 2;
+            state.prevnpt = -1;
+            state.prevalgo = -1;
+            minlm.minlmcreatefgh(k, state.c, state.optstate);
+            lsfitclearrequestfields(state);
+            state.rstate.ia = new int[4+1];
+            state.rstate.ra = new double[2+1];
+            state.rstate.stage = -1;
+        }
+
+
+        /*************************************************************************
+        Stopping conditions for nonlinear least squares fitting.
+
+        INPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+            EpsF    -   stopping criterion. Algorithm stops if
+                        |F(k+1)-F(k)| <= EpsF*max{|F(k)|, |F(k+1)|, 1}
+            EpsX    -   >=0
+                        The subroutine finishes its work if  on  k+1-th  iteration
+                        the condition |v|<=EpsX is fulfilled, where:
+                        * |.| means Euclidian norm
+                        * v - scaled step vector, v[i]=dx[i]/s[i]
+                        * dx - ste pvector, dx=X(k+1)-X(k)
+                        * s - scaling coefficients set by LSFitSetScale()
+            MaxIts  -   maximum number of iterations. If MaxIts=0, the  number  of
+                        iterations   is    unlimited.   Only   Levenberg-Marquardt
+                        iterations  are  counted  (L-BFGS/CG  iterations  are  NOT
+                        counted because their cost is very low compared to that of
+                        LM).
+
+        NOTE
+
+        Passing EpsF=0, EpsX=0 and MaxIts=0 (simultaneously) will lead to automatic
+        stopping criterion selection (according to the scheme used by MINLM unit).
+
+
+          -- ALGLIB --
+             Copyright 17.08.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void lsfitsetcond(lsfitstate state,
+            double epsf,
+            double epsx,
+            int maxits)
+        {
+            alglib.ap.assert(math.isfinite(epsf), "LSFitSetCond: EpsF is not finite!");
+            alglib.ap.assert((double)(epsf)>=(double)(0), "LSFitSetCond: negative EpsF!");
+            alglib.ap.assert(math.isfinite(epsx), "LSFitSetCond: EpsX is not finite!");
+            alglib.ap.assert((double)(epsx)>=(double)(0), "LSFitSetCond: negative EpsX!");
+            alglib.ap.assert(maxits>=0, "LSFitSetCond: negative MaxIts!");
+            state.epsf = epsf;
+            state.epsx = epsx;
+            state.maxits = maxits;
+        }
+
+
+        /*************************************************************************
+        This function sets maximum step length
+
+        INPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+            StpMax  -   maximum step length, >=0. Set StpMax to 0.0,  if you don't
+                        want to limit step length.
+
+        Use this subroutine when you optimize target function which contains exp()
+        or  other  fast  growing  functions,  and optimization algorithm makes too
+        large  steps  which  leads  to overflow. This function allows us to reject
+        steps  that  are  too  large  (and  therefore  expose  us  to the possible
+        overflow) without actually calculating function value at the x+stp*d.
+
+        NOTE: non-zero StpMax leads to moderate  performance  degradation  because
+        intermediate  step  of  preconditioned L-BFGS optimization is incompatible
+        with limits on step size.
+
+          -- ALGLIB --
+             Copyright 02.04.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void lsfitsetstpmax(lsfitstate state,
+            double stpmax)
+        {
+            alglib.ap.assert((double)(stpmax)>=(double)(0), "LSFitSetStpMax: StpMax<0!");
+            state.stpmax = stpmax;
+        }
+
+
+        /*************************************************************************
+        This function turns on/off reporting.
+
+        INPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+            NeedXRep-   whether iteration reports are needed or not
+            
+        When reports are needed, State.C (current parameters) and State.F (current
+        value of fitting function) are reported.
+
+
+          -- ALGLIB --
+             Copyright 15.08.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void lsfitsetxrep(lsfitstate state,
+            bool needxrep)
+        {
+            state.xrep = needxrep;
+        }
+
+
+        /*************************************************************************
+        This function sets scaling coefficients for underlying optimizer.
+
+        ALGLIB optimizers use scaling matrices to test stopping  conditions  (step
+        size and gradient are scaled before comparison with tolerances).  Scale of
+        the I-th variable is a translation invariant measure of:
+        a) "how large" the variable is
+        b) how large the step should be to make significant changes in the function
+
+        Generally, scale is NOT considered to be a form of preconditioner.  But LM
+        optimizer is unique in that it uses scaling matrix both  in  the  stopping
+        condition tests and as Marquardt damping factor.
+
+        Proper scaling is very important for the algorithm performance. It is less
+        important for the quality of results, but still has some influence (it  is
+        easier  to  converge  when  variables  are  properly  scaled, so premature
+        stopping is possible when very badly scalled variables are  combined  with
+        relaxed stopping conditions).
+
+        INPUT PARAMETERS:
+            State   -   structure stores algorithm state
+            S       -   array[N], non-zero scaling coefficients
+                        S[i] may be negative, sign doesn't matter.
+
+          -- ALGLIB --
+             Copyright 14.01.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void lsfitsetscale(lsfitstate state,
+            double[] s)
+        {
+            int i = 0;
+
+            alglib.ap.assert(alglib.ap.len(s)>=state.k, "LSFitSetScale: Length(S)<K");
+            for(i=0; i<=state.k-1; i++)
+            {
+                alglib.ap.assert(math.isfinite(s[i]), "LSFitSetScale: S contains infinite or NAN elements");
+                alglib.ap.assert((double)(s[i])!=(double)(0), "LSFitSetScale: S contains infinite or NAN elements");
+                state.s[i] = s[i];
+            }
+        }
+
+
+        /*************************************************************************
+        This function sets boundary constraints for underlying optimizer
+
+        Boundary constraints are inactive by default (after initial creation).
+        They are preserved until explicitly turned off with another SetBC() call.
+
+        INPUT PARAMETERS:
+            State   -   structure stores algorithm state
+            BndL    -   lower bounds, array[K].
+                        If some (all) variables are unbounded, you may specify
+                        very small number or -INF (latter is recommended because
+                        it will allow solver to use better algorithm).
+            BndU    -   upper bounds, array[K].
+                        If some (all) variables are unbounded, you may specify
+                        very large number or +INF (latter is recommended because
+                        it will allow solver to use better algorithm).
+
+        NOTE 1: it is possible to specify BndL[i]=BndU[i]. In this case I-th
+        variable will be "frozen" at X[i]=BndL[i]=BndU[i].
+
+        NOTE 2: unlike other constrained optimization algorithms, this solver  has
+        following useful properties:
+        * bound constraints are always satisfied exactly
+        * function is evaluated only INSIDE area specified by bound constraints
+
+          -- ALGLIB --
+             Copyright 14.01.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void lsfitsetbc(lsfitstate state,
+            double[] bndl,
+            double[] bndu)
+        {
+            int i = 0;
+            int k = 0;
+
+            k = state.k;
+            alglib.ap.assert(alglib.ap.len(bndl)>=k, "LSFitSetBC: Length(BndL)<K");
+            alglib.ap.assert(alglib.ap.len(bndu)>=k, "LSFitSetBC: Length(BndU)<K");
+            for(i=0; i<=k-1; i++)
+            {
+                alglib.ap.assert(math.isfinite(bndl[i]) || Double.IsNegativeInfinity(bndl[i]), "LSFitSetBC: BndL contains NAN or +INF");
+                alglib.ap.assert(math.isfinite(bndu[i]) || Double.IsPositiveInfinity(bndu[i]), "LSFitSetBC: BndU contains NAN or -INF");
+                if( math.isfinite(bndl[i]) && math.isfinite(bndu[i]) )
+                {
+                    alglib.ap.assert((double)(bndl[i])<=(double)(bndu[i]), "LSFitSetBC: BndL[i]>BndU[i]");
+                }
+                state.bndl[i] = bndl[i];
+                state.bndu[i] = bndu[i];
+            }
+        }
+
+
+        /*************************************************************************
+        NOTES:
+
+        1. this algorithm is somewhat unusual because it works with  parameterized
+           function f(C,X), where X is a function argument (we  have  many  points
+           which are characterized by different  argument  values),  and  C  is  a
+           parameter to fit.
+
+           For example, if we want to do linear fit by f(c0,c1,x) = c0*x+c1,  then
+           x will be argument, and {c0,c1} will be parameters.
+           
+           It is important to understand that this algorithm finds minimum in  the
+           space of function PARAMETERS (not arguments), so it  needs  derivatives
+           of f() with respect to C, not X.
+           
+           In the example above it will need f=c0*x+c1 and {df/dc0,df/dc1} = {x,1}
+           instead of {df/dx} = {c0}.
+
+        2. Callback functions accept C as the first parameter, and X as the second
+
+        3. If  state  was  created  with  LSFitCreateFG(),  algorithm  needs  just
+           function   and   its   gradient,   but   if   state   was  created with
+           LSFitCreateFGH(), algorithm will need function, gradient and Hessian.
+           
+           According  to  the  said  above,  there  ase  several  versions of this
+           function, which accept different sets of callbacks.
+           
+           This flexibility opens way to subtle errors - you may create state with
+           LSFitCreateFGH() (optimization using Hessian), but call function  which
+           does not accept Hessian. So when algorithm will request Hessian,  there
+           will be no callback to call. In this case exception will be thrown.
+           
+           Be careful to avoid such errors because there is no way to find them at
+           compile time - you can see them at runtime only.
+
+          -- ALGLIB --
+             Copyright 17.08.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static bool lsfititeration(lsfitstate state)
+        {
+            bool result = new bool();
+            int n = 0;
+            int m = 0;
+            int k = 0;
+            int i = 0;
+            int j = 0;
+            double v = 0;
+            double vv = 0;
+            double relcnt = 0;
+            int i_ = 0;
+
+            
+            //
+            // Reverse communication preparations
+            // I know it looks ugly, but it works the same way
+            // anywhere from C++ to Python.
+            //
+            // This code initializes locals by:
+            // * random values determined during code
+            //   generation - on first subroutine call
+            // * values from previous call - on subsequent calls
+            //
+            if( state.rstate.stage>=0 )
+            {
+                n = state.rstate.ia[0];
+                m = state.rstate.ia[1];
+                k = state.rstate.ia[2];
+                i = state.rstate.ia[3];
+                j = state.rstate.ia[4];
+                v = state.rstate.ra[0];
+                vv = state.rstate.ra[1];
+                relcnt = state.rstate.ra[2];
+            }
+            else
+            {
+                n = -983;
+                m = -989;
+                k = -834;
+                i = 900;
+                j = -287;
+                v = 364;
+                vv = 214;
+                relcnt = -338;
+            }
+            if( state.rstate.stage==0 )
+            {
+                goto lbl_0;
+            }
+            if( state.rstate.stage==1 )
+            {
+                goto lbl_1;
+            }
+            if( state.rstate.stage==2 )
+            {
+                goto lbl_2;
+            }
+            if( state.rstate.stage==3 )
+            {
+                goto lbl_3;
+            }
+            if( state.rstate.stage==4 )
+            {
+                goto lbl_4;
+            }
+            if( state.rstate.stage==5 )
+            {
+                goto lbl_5;
+            }
+            if( state.rstate.stage==6 )
+            {
+                goto lbl_6;
+            }
+            
+            //
+            // Routine body
+            //
+            
+            //
+            // init
+            //
+            if( state.wkind==1 )
+            {
+                alglib.ap.assert(state.npoints==state.nweights, "LSFitFit: number of points is not equal to the number of weights");
+            }
+            n = state.npoints;
+            m = state.m;
+            k = state.k;
+            minlm.minlmsetcond(state.optstate, 0.0, state.epsf, state.epsx, state.maxits);
+            minlm.minlmsetstpmax(state.optstate, state.stpmax);
+            minlm.minlmsetxrep(state.optstate, state.xrep);
+            minlm.minlmsetscale(state.optstate, state.s);
+            minlm.minlmsetbc(state.optstate, state.bndl, state.bndu);
+            
+            //
+            // Optimize
+            //
+        lbl_7:
+            if( !minlm.minlmiteration(state.optstate) )
+            {
+                goto lbl_8;
+            }
+            if( !state.optstate.needfi )
+            {
+                goto lbl_9;
+            }
+            
+            //
+            // calculate f[] = wi*(f(xi,c)-yi)
+            //
+            i = 0;
+        lbl_11:
+            if( i>n-1 )
+            {
+                goto lbl_13;
+            }
+            for(i_=0; i_<=k-1;i_++)
+            {
+                state.c[i_] = state.optstate.x[i_];
+            }
+            for(i_=0; i_<=m-1;i_++)
+            {
+                state.x[i_] = state.taskx[i,i_];
+            }
+            state.pointindex = i;
+            lsfitclearrequestfields(state);
+            state.needf = true;
+            state.rstate.stage = 0;
+            goto lbl_rcomm;
+        lbl_0:
+            state.needf = false;
+            if( state.wkind==1 )
+            {
+                vv = state.w[i];
+            }
+            else
+            {
+                vv = 1.0;
+            }
+            state.optstate.fi[i] = vv*(state.f-state.tasky[i]);
+            i = i+1;
+            goto lbl_11;
+        lbl_13:
+            goto lbl_7;
+        lbl_9:
+            if( !state.optstate.needf )
+            {
+                goto lbl_14;
+            }
+            
+            //
+            // calculate F = sum (wi*(f(xi,c)-yi))^2
+            //
+            state.optstate.f = 0;
+            i = 0;
+        lbl_16:
+            if( i>n-1 )
+            {
+                goto lbl_18;
+            }
+            for(i_=0; i_<=k-1;i_++)
+            {
+                state.c[i_] = state.optstate.x[i_];
+            }
+            for(i_=0; i_<=m-1;i_++)
+            {
+                state.x[i_] = state.taskx[i,i_];
+            }
+            state.pointindex = i;
+            lsfitclearrequestfields(state);
+            state.needf = true;
+            state.rstate.stage = 1;
+            goto lbl_rcomm;
+        lbl_1:
+            state.needf = false;
+            if( state.wkind==1 )
+            {
+                vv = state.w[i];
+            }
+            else
+            {
+                vv = 1.0;
+            }
+            state.optstate.f = state.optstate.f+math.sqr(vv*(state.f-state.tasky[i]));
+            i = i+1;
+            goto lbl_16;
+        lbl_18:
+            goto lbl_7;
+        lbl_14:
+            if( !state.optstate.needfg )
+            {
+                goto lbl_19;
+            }
+            
+            //
+            // calculate F/gradF
+            //
+            state.optstate.f = 0;
+            for(i=0; i<=k-1; i++)
+            {
+                state.optstate.g[i] = 0;
+            }
+            i = 0;
+        lbl_21:
+            if( i>n-1 )
+            {
+                goto lbl_23;
+            }
+            for(i_=0; i_<=k-1;i_++)
+            {
+                state.c[i_] = state.optstate.x[i_];
+            }
+            for(i_=0; i_<=m-1;i_++)
+            {
+                state.x[i_] = state.taskx[i,i_];
+            }
+            state.pointindex = i;
+            lsfitclearrequestfields(state);
+            state.needfg = true;
+            state.rstate.stage = 2;
+            goto lbl_rcomm;
+        lbl_2:
+            state.needfg = false;
+            if( state.wkind==1 )
+            {
+                vv = state.w[i];
+            }
+            else
+            {
+                vv = 1.0;
+            }
+            state.optstate.f = state.optstate.f+math.sqr(vv*(state.f-state.tasky[i]));
+            v = math.sqr(vv)*2*(state.f-state.tasky[i]);
+            for(i_=0; i_<=k-1;i_++)
+            {
+                state.optstate.g[i_] = state.optstate.g[i_] + v*state.g[i_];
+            }
+            i = i+1;
+            goto lbl_21;
+        lbl_23:
+            goto lbl_7;
+        lbl_19:
+            if( !state.optstate.needfij )
+            {
+                goto lbl_24;
+            }
+            
+            //
+            // calculate Fi/jac(Fi)
+            //
+            i = 0;
+        lbl_26:
+            if( i>n-1 )
+            {
+                goto lbl_28;
+            }
+            for(i_=0; i_<=k-1;i_++)
+            {
+                state.c[i_] = state.optstate.x[i_];
+            }
+            for(i_=0; i_<=m-1;i_++)
+            {
+                state.x[i_] = state.taskx[i,i_];
+            }
+            state.pointindex = i;
+            lsfitclearrequestfields(state);
+            state.needfg = true;
+            state.rstate.stage = 3;
+            goto lbl_rcomm;
+        lbl_3:
+            state.needfg = false;
+            if( state.wkind==1 )
+            {
+                vv = state.w[i];
+            }
+            else
+            {
+                vv = 1.0;
+            }
+            state.optstate.fi[i] = vv*(state.f-state.tasky[i]);
+            for(i_=0; i_<=k-1;i_++)
+            {
+                state.optstate.j[i,i_] = vv*state.g[i_];
+            }
+            i = i+1;
+            goto lbl_26;
+        lbl_28:
+            goto lbl_7;
+        lbl_24:
+            if( !state.optstate.needfgh )
+            {
+                goto lbl_29;
+            }
+            
+            //
+            // calculate F/grad(F)/hess(F)
+            //
+            state.optstate.f = 0;
+            for(i=0; i<=k-1; i++)
+            {
+                state.optstate.g[i] = 0;
+            }
+            for(i=0; i<=k-1; i++)
+            {
+                for(j=0; j<=k-1; j++)
+                {
+                    state.optstate.h[i,j] = 0;
+                }
+            }
+            i = 0;
+        lbl_31:
+            if( i>n-1 )
+            {
+                goto lbl_33;
+            }
+            for(i_=0; i_<=k-1;i_++)
+            {
+                state.c[i_] = state.optstate.x[i_];
+            }
+            for(i_=0; i_<=m-1;i_++)
+            {
+                state.x[i_] = state.taskx[i,i_];
+            }
+            state.pointindex = i;
+            lsfitclearrequestfields(state);
+            state.needfgh = true;
+            state.rstate.stage = 4;
+            goto lbl_rcomm;
+        lbl_4:
+            state.needfgh = false;
+            if( state.wkind==1 )
+            {
+                vv = state.w[i];
+            }
+            else
+            {
+                vv = 1.0;
+            }
+            state.optstate.f = state.optstate.f+math.sqr(vv*(state.f-state.tasky[i]));
+            v = math.sqr(vv)*2*(state.f-state.tasky[i]);
+            for(i_=0; i_<=k-1;i_++)
+            {
+                state.optstate.g[i_] = state.optstate.g[i_] + v*state.g[i_];
+            }
+            for(j=0; j<=k-1; j++)
+            {
+                v = 2*math.sqr(vv)*state.g[j];
+                for(i_=0; i_<=k-1;i_++)
+                {
+                    state.optstate.h[j,i_] = state.optstate.h[j,i_] + v*state.g[i_];
+                }
+                v = 2*math.sqr(vv)*(state.f-state.tasky[i]);
+                for(i_=0; i_<=k-1;i_++)
+                {
+                    state.optstate.h[j,i_] = state.optstate.h[j,i_] + v*state.h[j,i_];
+                }
+            }
+            i = i+1;
+            goto lbl_31;
+        lbl_33:
+            goto lbl_7;
+        lbl_29:
+            if( !state.optstate.xupdated )
+            {
+                goto lbl_34;
+            }
+            
+            //
+            // Report new iteration
+            //
+            for(i_=0; i_<=k-1;i_++)
+            {
+                state.c[i_] = state.optstate.x[i_];
+            }
+            state.f = state.optstate.f;
+            lsfitclearrequestfields(state);
+            state.xupdated = true;
+            state.rstate.stage = 5;
+            goto lbl_rcomm;
+        lbl_5:
+            state.xupdated = false;
+            goto lbl_7;
+        lbl_34:
+            goto lbl_7;
+        lbl_8:
+            minlm.minlmresults(state.optstate, ref state.c, state.optrep);
+            state.repterminationtype = state.optrep.terminationtype;
+            state.repiterationscount = state.optrep.iterationscount;
+            
+            //
+            // calculate errors
+            //
+            if( state.repterminationtype<=0 )
+            {
+                goto lbl_36;
+            }
+            state.reprmserror = 0;
+            state.repwrmserror = 0;
+            state.repavgerror = 0;
+            state.repavgrelerror = 0;
+            state.repmaxerror = 0;
+            relcnt = 0;
+            i = 0;
+        lbl_38:
+            if( i>n-1 )
+            {
+                goto lbl_40;
+            }
+            for(i_=0; i_<=k-1;i_++)
+            {
+                state.c[i_] = state.c[i_];
+            }
+            for(i_=0; i_<=m-1;i_++)
+            {
+                state.x[i_] = state.taskx[i,i_];
+            }
+            state.pointindex = i;
+            lsfitclearrequestfields(state);
+            state.needf = true;
+            state.rstate.stage = 6;
+            goto lbl_rcomm;
+        lbl_6:
+            state.needf = false;
+            v = state.f;
+            if( state.wkind==1 )
+            {
+                vv = state.w[i];
+            }
+            else
+            {
+                vv = 1.0;
+            }
+            state.reprmserror = state.reprmserror+math.sqr(v-state.tasky[i]);
+            state.repwrmserror = state.repwrmserror+math.sqr(vv*(v-state.tasky[i]));
+            state.repavgerror = state.repavgerror+Math.Abs(v-state.tasky[i]);
+            if( (double)(state.tasky[i])!=(double)(0) )
+            {
+                state.repavgrelerror = state.repavgrelerror+Math.Abs(v-state.tasky[i])/Math.Abs(state.tasky[i]);
+                relcnt = relcnt+1;
+            }
+            state.repmaxerror = Math.Max(state.repmaxerror, Math.Abs(v-state.tasky[i]));
+            i = i+1;
+            goto lbl_38;
+        lbl_40:
+            state.reprmserror = Math.Sqrt(state.reprmserror/n);
+            state.repwrmserror = Math.Sqrt(state.repwrmserror/n);
+            state.repavgerror = state.repavgerror/n;
+            if( (double)(relcnt)!=(double)(0) )
+            {
+                state.repavgrelerror = state.repavgrelerror/relcnt;
+            }
+        lbl_36:
+            result = false;
+            return result;
+            
+            //
+            // Saving state
+            //
+        lbl_rcomm:
+            result = true;
+            state.rstate.ia[0] = n;
+            state.rstate.ia[1] = m;
+            state.rstate.ia[2] = k;
+            state.rstate.ia[3] = i;
+            state.rstate.ia[4] = j;
+            state.rstate.ra[0] = v;
+            state.rstate.ra[1] = vv;
+            state.rstate.ra[2] = relcnt;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Nonlinear least squares fitting results.
+
+        Called after return from LSFitFit().
+
+        INPUT PARAMETERS:
+            State   -   algorithm state
+
+        OUTPUT PARAMETERS:
+            Info    -   completetion code:
+                            *  1    relative function improvement is no more than
+                                    EpsF.
+                            *  2    relative step is no more than EpsX.
+                            *  4    gradient norm is no more than EpsG
+                            *  5    MaxIts steps was taken
+                            *  7    stopping conditions are too stringent,
+                                    further improvement is impossible
+            C       -   array[0..K-1], solution
+            Rep     -   optimization report. Following fields are set:
+                        * Rep.TerminationType completetion code:
+                        * RMSError          rms error on the (X,Y).
+                        * AvgError          average error on the (X,Y).
+                        * AvgRelError       average relative error on the non-zero Y
+                        * MaxError          maximum error
+                                            NON-WEIGHTED ERRORS ARE CALCULATED
+                        * WRMSError         weighted rms error on the (X,Y).
+
+
+          -- ALGLIB --
+             Copyright 17.08.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void lsfitresults(lsfitstate state,
+            ref int info,
+            ref double[] c,
+            lsfitreport rep)
+        {
+            int i_ = 0;
+
+            info = 0;
+            c = new double[0];
+
+            info = state.repterminationtype;
+            if( info>0 )
+            {
+                c = new double[state.k];
+                for(i_=0; i_<=state.k-1;i_++)
+                {
+                    c[i_] = state.c[i_];
+                }
+                rep.rmserror = state.reprmserror;
+                rep.wrmserror = state.repwrmserror;
+                rep.avgerror = state.repavgerror;
+                rep.avgrelerror = state.repavgrelerror;
+                rep.maxerror = state.repmaxerror;
+                rep.iterationscount = state.repiterationscount;
+            }
+        }
+
+
+        /*************************************************************************
+        Internal subroutine: automatic scaling for LLS tasks.
+        NEVER CALL IT DIRECTLY!
+
+        Maps abscissas to [-1,1], standartizes ordinates and correspondingly scales
+        constraints. It also scales weights so that max(W[i])=1
+
+        Transformations performed:
+        * X, XC         [XA,XB] => [-1,+1]
+                        transformation makes min(X)=-1, max(X)=+1
+
+        * Y             [SA,SB] => [0,1]
+                        transformation makes mean(Y)=0, stddev(Y)=1
+                        
+        * YC            transformed accordingly to SA, SB, DC[I]
+
+          -- ALGLIB PROJECT --
+             Copyright 08.09.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void lsfitscalexy(ref double[] x,
+            ref double[] y,
+            ref double[] w,
+            int n,
+            ref double[] xc,
+            ref double[] yc,
+            int[] dc,
+            int k,
+            ref double xa,
+            ref double xb,
+            ref double sa,
+            ref double sb,
+            ref double[] xoriginal,
+            ref double[] yoriginal)
+        {
+            double xmin = 0;
+            double xmax = 0;
+            int i = 0;
+            double mx = 0;
+            int i_ = 0;
+
+            xa = 0;
+            xb = 0;
+            sa = 0;
+            sb = 0;
+            xoriginal = new double[0];
+            yoriginal = new double[0];
+
+            alglib.ap.assert(n>=1, "LSFitScaleXY: incorrect N");
+            alglib.ap.assert(k>=0, "LSFitScaleXY: incorrect K");
+            
+            //
+            // Calculate xmin/xmax.
+            // Force xmin<>xmax.
+            //
+            xmin = x[0];
+            xmax = x[0];
+            for(i=1; i<=n-1; i++)
+            {
+                xmin = Math.Min(xmin, x[i]);
+                xmax = Math.Max(xmax, x[i]);
+            }
+            for(i=0; i<=k-1; i++)
+            {
+                xmin = Math.Min(xmin, xc[i]);
+                xmax = Math.Max(xmax, xc[i]);
+            }
+            if( (double)(xmin)==(double)(xmax) )
+            {
+                if( (double)(xmin)==(double)(0) )
+                {
+                    xmin = -1;
+                    xmax = 1;
+                }
+                else
+                {
+                    if( (double)(xmin)>(double)(0) )
+                    {
+                        xmin = 0.5*xmin;
+                    }
+                    else
+                    {
+                        xmax = 0.5*xmax;
+                    }
+                }
+            }
+            
+            //
+            // Transform abscissas: map [XA,XB] to [0,1]
+            //
+            // Store old X[] in XOriginal[] (it will be used
+            // to calculate relative error).
+            //
+            xoriginal = new double[n];
+            for(i_=0; i_<=n-1;i_++)
+            {
+                xoriginal[i_] = x[i_];
+            }
+            xa = xmin;
+            xb = xmax;
+            for(i=0; i<=n-1; i++)
+            {
+                x[i] = 2*(x[i]-0.5*(xa+xb))/(xb-xa);
+            }
+            for(i=0; i<=k-1; i++)
+            {
+                alglib.ap.assert(dc[i]>=0, "LSFitScaleXY: internal error!");
+                xc[i] = 2*(xc[i]-0.5*(xa+xb))/(xb-xa);
+                yc[i] = yc[i]*Math.Pow(0.5*(xb-xa), dc[i]);
+            }
+            
+            //
+            // Transform function values: map [SA,SB] to [0,1]
+            // SA = mean(Y),
+            // SB = SA+stddev(Y).
+            //
+            // Store old Y[] in YOriginal[] (it will be used
+            // to calculate relative error).
+            //
+            yoriginal = new double[n];
+            for(i_=0; i_<=n-1;i_++)
+            {
+                yoriginal[i_] = y[i_];
+            }
+            sa = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                sa = sa+y[i];
+            }
+            sa = sa/n;
+            sb = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                sb = sb+math.sqr(y[i]-sa);
+            }
+            sb = Math.Sqrt(sb/n)+sa;
+            if( (double)(sb)==(double)(sa) )
+            {
+                sb = 2*sa;
+            }
+            if( (double)(sb)==(double)(sa) )
+            {
+                sb = sa+1;
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                y[i] = (y[i]-sa)/(sb-sa);
+            }
+            for(i=0; i<=k-1; i++)
+            {
+                if( dc[i]==0 )
+                {
+                    yc[i] = (yc[i]-sa)/(sb-sa);
+                }
+                else
+                {
+                    yc[i] = yc[i]/(sb-sa);
+                }
+            }
+            
+            //
+            // Scale weights
+            //
+            mx = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                mx = Math.Max(mx, Math.Abs(w[i]));
+            }
+            if( (double)(mx)!=(double)(0) )
+            {
+                for(i=0; i<=n-1; i++)
+                {
+                    w[i] = w[i]/mx;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Internal spline fitting subroutine
+
+          -- ALGLIB PROJECT --
+             Copyright 08.09.2009 by Bochkanov Sergey
+        *************************************************************************/
+        private static void spline1dfitinternal(int st,
+            double[] x,
+            double[] y,
+            double[] w,
+            int n,
+            double[] xc,
+            double[] yc,
+            int[] dc,
+            int k,
+            int m,
+            ref int info,
+            spline1d.spline1dinterpolant s,
+            spline1dfitreport rep)
+        {
+            double[,] fmatrix = new double[0,0];
+            double[,] cmatrix = new double[0,0];
+            double[] y2 = new double[0];
+            double[] w2 = new double[0];
+            double[] sx = new double[0];
+            double[] sy = new double[0];
+            double[] sd = new double[0];
+            double[] tmp = new double[0];
+            double[] xoriginal = new double[0];
+            double[] yoriginal = new double[0];
+            lsfitreport lrep = new lsfitreport();
+            double v0 = 0;
+            double v1 = 0;
+            double v2 = 0;
+            double mx = 0;
+            spline1d.spline1dinterpolant s2 = new spline1d.spline1dinterpolant();
+            int i = 0;
+            int j = 0;
+            int relcnt = 0;
+            double xa = 0;
+            double xb = 0;
+            double sa = 0;
+            double sb = 0;
+            double bl = 0;
+            double br = 0;
+            double decay = 0;
+            int i_ = 0;
+
+            x = (double[])x.Clone();
+            y = (double[])y.Clone();
+            w = (double[])w.Clone();
+            xc = (double[])xc.Clone();
+            yc = (double[])yc.Clone();
+            info = 0;
+
+            alglib.ap.assert(st==0 || st==1, "Spline1DFit: internal error!");
+            if( st==0 && m<4 )
+            {
+                info = -1;
+                return;
+            }
+            if( st==1 && m<4 )
+            {
+                info = -1;
+                return;
+            }
+            if( (n<1 || k<0) || k>=m )
+            {
+                info = -1;
+                return;
+            }
+            for(i=0; i<=k-1; i++)
+            {
+                info = 0;
+                if( dc[i]<0 )
+                {
+                    info = -1;
+                }
+                if( dc[i]>1 )
+                {
+                    info = -1;
+                }
+                if( info<0 )
+                {
+                    return;
+                }
+            }
+            if( st==1 && m%2!=0 )
+            {
+                
+                //
+                // Hermite fitter must have even number of basis functions
+                //
+                info = -2;
+                return;
+            }
+            
+            //
+            // weight decay for correct handling of task which becomes
+            // degenerate after constraints are applied
+            //
+            decay = 10000*math.machineepsilon;
+            
+            //
+            // Scale X, Y, XC, YC
+            //
+            lsfitscalexy(ref x, ref y, ref w, n, ref xc, ref yc, dc, k, ref xa, ref xb, ref sa, ref sb, ref xoriginal, ref yoriginal);
+            
+            //
+            // allocate space, initialize:
+            // * SX     -   grid for basis functions
+            // * SY     -   values of basis functions at grid points
+            // * FMatrix-   values of basis functions at X[]
+            // * CMatrix-   values (derivatives) of basis functions at XC[]
+            //
+            y2 = new double[n+m];
+            w2 = new double[n+m];
+            fmatrix = new double[n+m, m];
+            if( k>0 )
+            {
+                cmatrix = new double[k, m+1];
+            }
+            if( st==0 )
+            {
+                
+                //
+                // allocate space for cubic spline
+                //
+                sx = new double[m-2];
+                sy = new double[m-2];
+                for(j=0; j<=m-2-1; j++)
+                {
+                    sx[j] = (double)(2*j)/(double)(m-2-1)-1;
+                }
+            }
+            if( st==1 )
+            {
+                
+                //
+                // allocate space for Hermite spline
+                //
+                sx = new double[m/2];
+                sy = new double[m/2];
+                sd = new double[m/2];
+                for(j=0; j<=m/2-1; j++)
+                {
+                    sx[j] = (double)(2*j)/(double)(m/2-1)-1;
+                }
+            }
+            
+            //
+            // Prepare design and constraints matrices:
+            // * fill constraints matrix
+            // * fill first N rows of design matrix with values
+            // * fill next M rows of design matrix with regularizing term
+            // * append M zeros to Y
+            // * append M elements, mean(abs(W)) each, to W
+            //
+            for(j=0; j<=m-1; j++)
+            {
+                
+                //
+                // prepare Jth basis function
+                //
+                if( st==0 )
+                {
+                    
+                    //
+                    // cubic spline basis
+                    //
+                    for(i=0; i<=m-2-1; i++)
+                    {
+                        sy[i] = 0;
+                    }
+                    bl = 0;
+                    br = 0;
+                    if( j<m-2 )
+                    {
+                        sy[j] = 1;
+                    }
+                    if( j==m-2 )
+                    {
+                        bl = 1;
+                    }
+                    if( j==m-1 )
+                    {
+                        br = 1;
+                    }
+                    spline1d.spline1dbuildcubic(sx, sy, m-2, 1, bl, 1, br, s2);
+                }
+                if( st==1 )
+                {
+                    
+                    //
+                    // Hermite basis
+                    //
+                    for(i=0; i<=m/2-1; i++)
+                    {
+                        sy[i] = 0;
+                        sd[i] = 0;
+                    }
+                    if( j%2==0 )
+                    {
+                        sy[j/2] = 1;
+                    }
+                    else
+                    {
+                        sd[j/2] = 1;
+                    }
+                    spline1d.spline1dbuildhermite(sx, sy, sd, m/2, s2);
+                }
+                
+                //
+                // values at X[], XC[]
+                //
+                for(i=0; i<=n-1; i++)
+                {
+                    fmatrix[i,j] = spline1d.spline1dcalc(s2, x[i]);
+                }
+                for(i=0; i<=k-1; i++)
+                {
+                    alglib.ap.assert(dc[i]>=0 && dc[i]<=2, "Spline1DFit: internal error!");
+                    spline1d.spline1ddiff(s2, xc[i], ref v0, ref v1, ref v2);
+                    if( dc[i]==0 )
+                    {
+                        cmatrix[i,j] = v0;
+                    }
+                    if( dc[i]==1 )
+                    {
+                        cmatrix[i,j] = v1;
+                    }
+                    if( dc[i]==2 )
+                    {
+                        cmatrix[i,j] = v2;
+                    }
+                }
+            }
+            for(i=0; i<=k-1; i++)
+            {
+                cmatrix[i,m] = yc[i];
+            }
+            for(i=0; i<=m-1; i++)
+            {
+                for(j=0; j<=m-1; j++)
+                {
+                    if( i==j )
+                    {
+                        fmatrix[n+i,j] = decay;
+                    }
+                    else
+                    {
+                        fmatrix[n+i,j] = 0;
+                    }
+                }
+            }
+            y2 = new double[n+m];
+            w2 = new double[n+m];
+            for(i_=0; i_<=n-1;i_++)
+            {
+                y2[i_] = y[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                w2[i_] = w[i_];
+            }
+            mx = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                mx = mx+Math.Abs(w[i]);
+            }
+            mx = mx/n;
+            for(i=0; i<=m-1; i++)
+            {
+                y2[n+i] = 0;
+                w2[n+i] = mx;
+            }
+            
+            //
+            // Solve constrained task
+            //
+            if( k>0 )
+            {
+                
+                //
+                // solve using regularization
+                //
+                lsfitlinearwc(y2, w2, fmatrix, cmatrix, n+m, m, k, ref info, ref tmp, lrep);
+            }
+            else
+            {
+                
+                //
+                // no constraints, no regularization needed
+                //
+                lsfitlinearwc(y, w, fmatrix, cmatrix, n, m, k, ref info, ref tmp, lrep);
+            }
+            if( info<0 )
+            {
+                return;
+            }
+            
+            //
+            // Generate spline and scale it
+            //
+            if( st==0 )
+            {
+                
+                //
+                // cubic spline basis
+                //
+                for(i_=0; i_<=m-2-1;i_++)
+                {
+                    sy[i_] = tmp[i_];
+                }
+                spline1d.spline1dbuildcubic(sx, sy, m-2, 1, tmp[m-2], 1, tmp[m-1], s);
+            }
+            if( st==1 )
+            {
+                
+                //
+                // Hermite basis
+                //
+                for(i=0; i<=m/2-1; i++)
+                {
+                    sy[i] = tmp[2*i];
+                    sd[i] = tmp[2*i+1];
+                }
+                spline1d.spline1dbuildhermite(sx, sy, sd, m/2, s);
+            }
+            spline1d.spline1dlintransx(s, 2/(xb-xa), -((xa+xb)/(xb-xa)));
+            spline1d.spline1dlintransy(s, sb-sa, sa);
+            
+            //
+            // Scale absolute errors obtained from LSFitLinearW.
+            // Relative error should be calculated separately
+            // (because of shifting/scaling of the task)
+            //
+            rep.taskrcond = lrep.taskrcond;
+            rep.rmserror = lrep.rmserror*(sb-sa);
+            rep.avgerror = lrep.avgerror*(sb-sa);
+            rep.maxerror = lrep.maxerror*(sb-sa);
+            rep.avgrelerror = 0;
+            relcnt = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                if( (double)(yoriginal[i])!=(double)(0) )
+                {
+                    rep.avgrelerror = rep.avgrelerror+Math.Abs(spline1d.spline1dcalc(s, xoriginal[i])-yoriginal[i])/Math.Abs(yoriginal[i]);
+                    relcnt = relcnt+1;
+                }
+            }
+            if( relcnt!=0 )
+            {
+                rep.avgrelerror = rep.avgrelerror/relcnt;
+            }
+        }
+
+
+        /*************************************************************************
+        Internal fitting subroutine
+        *************************************************************************/
+        private static void lsfitlinearinternal(double[] y,
+            double[] w,
+            double[,] fmatrix,
+            int n,
+            int m,
+            ref int info,
+            ref double[] c,
+            lsfitreport rep)
+        {
+            double threshold = 0;
+            double[,] ft = new double[0,0];
+            double[,] q = new double[0,0];
+            double[,] l = new double[0,0];
+            double[,] r = new double[0,0];
+            double[] b = new double[0];
+            double[] wmod = new double[0];
+            double[] tau = new double[0];
+            int i = 0;
+            int j = 0;
+            double v = 0;
+            double[] sv = new double[0];
+            double[,] u = new double[0,0];
+            double[,] vt = new double[0,0];
+            double[] tmp = new double[0];
+            double[] utb = new double[0];
+            double[] sutb = new double[0];
+            int relcnt = 0;
+            int i_ = 0;
+
+            info = 0;
+            c = new double[0];
+
+            if( n<1 || m<1 )
+            {
+                info = -1;
+                return;
+            }
+            info = 1;
+            threshold = Math.Sqrt(math.machineepsilon);
+            
+            //
+            // Degenerate case, needs special handling
+            //
+            if( n<m )
+            {
+                
+                //
+                // Create design matrix.
+                //
+                ft = new double[n, m];
+                b = new double[n];
+                wmod = new double[n];
+                for(j=0; j<=n-1; j++)
+                {
+                    v = w[j];
+                    for(i_=0; i_<=m-1;i_++)
+                    {
+                        ft[j,i_] = v*fmatrix[j,i_];
+                    }
+                    b[j] = w[j]*y[j];
+                    wmod[j] = 1;
+                }
+                
+                //
+                // LQ decomposition and reduction to M=N
+                //
+                c = new double[m];
+                for(i=0; i<=m-1; i++)
+                {
+                    c[i] = 0;
+                }
+                rep.taskrcond = 0;
+                ortfac.rmatrixlq(ref ft, n, m, ref tau);
+                ortfac.rmatrixlqunpackq(ft, n, m, tau, n, ref q);
+                ortfac.rmatrixlqunpackl(ft, n, m, ref l);
+                lsfitlinearinternal(b, wmod, l, n, n, ref info, ref tmp, rep);
+                if( info<=0 )
+                {
+                    return;
+                }
+                for(i=0; i<=n-1; i++)
+                {
+                    v = tmp[i];
+                    for(i_=0; i_<=m-1;i_++)
+                    {
+                        c[i_] = c[i_] + v*q[i,i_];
+                    }
+                }
+                return;
+            }
+            
+            //
+            // N>=M. Generate design matrix and reduce to N=M using
+            // QR decomposition.
+            //
+            ft = new double[n, m];
+            b = new double[n];
+            for(j=0; j<=n-1; j++)
+            {
+                v = w[j];
+                for(i_=0; i_<=m-1;i_++)
+                {
+                    ft[j,i_] = v*fmatrix[j,i_];
+                }
+                b[j] = w[j]*y[j];
+            }
+            ortfac.rmatrixqr(ref ft, n, m, ref tau);
+            ortfac.rmatrixqrunpackq(ft, n, m, tau, m, ref q);
+            ortfac.rmatrixqrunpackr(ft, n, m, ref r);
+            tmp = new double[m];
+            for(i=0; i<=m-1; i++)
+            {
+                tmp[i] = 0;
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                v = b[i];
+                for(i_=0; i_<=m-1;i_++)
+                {
+                    tmp[i_] = tmp[i_] + v*q[i,i_];
+                }
+            }
+            b = new double[m];
+            for(i_=0; i_<=m-1;i_++)
+            {
+                b[i_] = tmp[i_];
+            }
+            
+            //
+            // R contains reduced MxM design upper triangular matrix,
+            // B contains reduced Mx1 right part.
+            //
+            // Determine system condition number and decide
+            // should we use triangular solver (faster) or
+            // SVD-based solver (more stable).
+            //
+            // We can use LU-based RCond estimator for this task.
+            //
+            rep.taskrcond = rcond.rmatrixlurcondinf(r, m);
+            if( (double)(rep.taskrcond)>(double)(threshold) )
+            {
+                
+                //
+                // use QR-based solver
+                //
+                c = new double[m];
+                c[m-1] = b[m-1]/r[m-1,m-1];
+                for(i=m-2; i>=0; i--)
+                {
+                    v = 0.0;
+                    for(i_=i+1; i_<=m-1;i_++)
+                    {
+                        v += r[i,i_]*c[i_];
+                    }
+                    c[i] = (b[i]-v)/r[i,i];
+                }
+            }
+            else
+            {
+                
+                //
+                // use SVD-based solver
+                //
+                if( !svd.rmatrixsvd(r, m, m, 1, 1, 2, ref sv, ref u, ref vt) )
+                {
+                    info = -4;
+                    return;
+                }
+                utb = new double[m];
+                sutb = new double[m];
+                for(i=0; i<=m-1; i++)
+                {
+                    utb[i] = 0;
+                }
+                for(i=0; i<=m-1; i++)
+                {
+                    v = b[i];
+                    for(i_=0; i_<=m-1;i_++)
+                    {
+                        utb[i_] = utb[i_] + v*u[i,i_];
+                    }
+                }
+                if( (double)(sv[0])>(double)(0) )
+                {
+                    rep.taskrcond = sv[m-1]/sv[0];
+                    for(i=0; i<=m-1; i++)
+                    {
+                        if( (double)(sv[i])>(double)(threshold*sv[0]) )
+                        {
+                            sutb[i] = utb[i]/sv[i];
+                        }
+                        else
+                        {
+                            sutb[i] = 0;
+                        }
+                    }
+                }
+                else
+                {
+                    rep.taskrcond = 0;
+                    for(i=0; i<=m-1; i++)
+                    {
+                        sutb[i] = 0;
+                    }
+                }
+                c = new double[m];
+                for(i=0; i<=m-1; i++)
+                {
+                    c[i] = 0;
+                }
+                for(i=0; i<=m-1; i++)
+                {
+                    v = sutb[i];
+                    for(i_=0; i_<=m-1;i_++)
+                    {
+                        c[i_] = c[i_] + v*vt[i,i_];
+                    }
+                }
+            }
+            
+            //
+            // calculate errors
+            //
+            rep.rmserror = 0;
+            rep.avgerror = 0;
+            rep.avgrelerror = 0;
+            rep.maxerror = 0;
+            relcnt = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                v = 0.0;
+                for(i_=0; i_<=m-1;i_++)
+                {
+                    v += fmatrix[i,i_]*c[i_];
+                }
+                rep.rmserror = rep.rmserror+math.sqr(v-y[i]);
+                rep.avgerror = rep.avgerror+Math.Abs(v-y[i]);
+                if( (double)(y[i])!=(double)(0) )
+                {
+                    rep.avgrelerror = rep.avgrelerror+Math.Abs(v-y[i])/Math.Abs(y[i]);
+                    relcnt = relcnt+1;
+                }
+                rep.maxerror = Math.Max(rep.maxerror, Math.Abs(v-y[i]));
+            }
+            rep.rmserror = Math.Sqrt(rep.rmserror/n);
+            rep.avgerror = rep.avgerror/n;
+            if( relcnt!=0 )
+            {
+                rep.avgrelerror = rep.avgrelerror/relcnt;
+            }
+        }
+
+
+        /*************************************************************************
+        Internal subroutine
+        *************************************************************************/
+        private static void lsfitclearrequestfields(lsfitstate state)
+        {
+            state.needf = false;
+            state.needfg = false;
+            state.needfgh = false;
+            state.xupdated = false;
+        }
+
+
+        /*************************************************************************
+        Internal subroutine, calculates barycentric basis functions.
+        Used for efficient simultaneous calculation of N basis functions.
+
+          -- ALGLIB --
+             Copyright 17.08.2009 by Bochkanov Sergey
+        *************************************************************************/
+        private static void barycentriccalcbasis(ratint.barycentricinterpolant b,
+            double t,
+            ref double[] y)
+        {
+            double s2 = 0;
+            double s = 0;
+            double v = 0;
+            int i = 0;
+            int j = 0;
+            int i_ = 0;
+
+            
+            //
+            // special case: N=1
+            //
+            if( b.n==1 )
+            {
+                y[0] = 1;
+                return;
+            }
+            
+            //
+            // Here we assume that task is normalized, i.e.:
+            // 1. abs(Y[i])<=1
+            // 2. abs(W[i])<=1
+            // 3. X[] is ordered
+            //
+            // First, we decide: should we use "safe" formula (guarded
+            // against overflow) or fast one?
+            //
+            s = Math.Abs(t-b.x[0]);
+            for(i=0; i<=b.n-1; i++)
+            {
+                v = b.x[i];
+                if( (double)(v)==(double)(t) )
+                {
+                    for(j=0; j<=b.n-1; j++)
+                    {
+                        y[j] = 0;
+                    }
+                    y[i] = 1;
+                    return;
+                }
+                v = Math.Abs(t-v);
+                if( (double)(v)<(double)(s) )
+                {
+                    s = v;
+                }
+            }
+            s2 = 0;
+            for(i=0; i<=b.n-1; i++)
+            {
+                v = s/(t-b.x[i]);
+                v = v*b.w[i];
+                y[i] = v;
+                s2 = s2+v;
+            }
+            v = 1/s2;
+            for(i_=0; i_<=b.n-1;i_++)
+            {
+                y[i_] = v*y[i_];
+            }
+        }
+
+
+        /*************************************************************************
+        This is internal function for Chebyshev fitting.
+
+        It assumes that input data are normalized:
+        * X/XC belong to [-1,+1],
+        * mean(Y)=0, stddev(Y)=1.
+
+        It does not checks inputs for errors.
+
+        This function is used to fit general (shifted) Chebyshev models, power
+        basis models or barycentric models.
+
+        INPUT PARAMETERS:
+            X   -   points, array[0..N-1].
+            Y   -   function values, array[0..N-1].
+            W   -   weights, array[0..N-1]
+            N   -   number of points, N>0.
+            XC  -   points where polynomial values/derivatives are constrained,
+                    array[0..K-1].
+            YC  -   values of constraints, array[0..K-1]
+            DC  -   array[0..K-1], types of constraints:
+                    * DC[i]=0   means that P(XC[i])=YC[i]
+                    * DC[i]=1   means that P'(XC[i])=YC[i]
+            K   -   number of constraints, 0<=K<M.
+                    K=0 means no constraints (XC/YC/DC are not used in such cases)
+            M   -   number of basis functions (= polynomial_degree + 1), M>=1
+
+        OUTPUT PARAMETERS:
+            Info-   same format as in LSFitLinearW() subroutine:
+                    * Info>0    task is solved
+                    * Info<=0   an error occured:
+                                -4 means inconvergence of internal SVD
+                                -3 means inconsistent constraints
+            C   -   interpolant in Chebyshev form; [-1,+1] is used as base interval
+            Rep -   report, same format as in LSFitLinearW() subroutine.
+                    Following fields are set:
+                    * RMSError      rms error on the (X,Y).
+                    * AvgError      average error on the (X,Y).
+                    * AvgRelError   average relative error on the non-zero Y
+                    * MaxError      maximum error
+                                    NON-WEIGHTED ERRORS ARE CALCULATED
+
+        IMPORTANT:
+            this subroitine doesn't calculate task's condition number for K<>0.
+
+          -- ALGLIB PROJECT --
+             Copyright 10.12.2009 by Bochkanov Sergey
+        *************************************************************************/
+        private static void internalchebyshevfit(double[] x,
+            double[] y,
+            double[] w,
+            int n,
+            double[] xc,
+            double[] yc,
+            int[] dc,
+            int k,
+            int m,
+            ref int info,
+            ref double[] c,
+            lsfitreport rep)
+        {
+            double[] y2 = new double[0];
+            double[] w2 = new double[0];
+            double[] tmp = new double[0];
+            double[] tmp2 = new double[0];
+            double[] tmpdiff = new double[0];
+            double[] bx = new double[0];
+            double[] by = new double[0];
+            double[] bw = new double[0];
+            double[,] fmatrix = new double[0,0];
+            double[,] cmatrix = new double[0,0];
+            int i = 0;
+            int j = 0;
+            double mx = 0;
+            double decay = 0;
+            int i_ = 0;
+
+            xc = (double[])xc.Clone();
+            yc = (double[])yc.Clone();
+            info = 0;
+            c = new double[0];
+
+            
+            //
+            // weight decay for correct handling of task which becomes
+            // degenerate after constraints are applied
+            //
+            decay = 10000*math.machineepsilon;
+            
+            //
+            // allocate space, initialize/fill:
+            // * FMatrix-   values of basis functions at X[]
+            // * CMatrix-   values (derivatives) of basis functions at XC[]
+            // * fill constraints matrix
+            // * fill first N rows of design matrix with values
+            // * fill next M rows of design matrix with regularizing term
+            // * append M zeros to Y
+            // * append M elements, mean(abs(W)) each, to W
+            //
+            y2 = new double[n+m];
+            w2 = new double[n+m];
+            tmp = new double[m];
+            tmpdiff = new double[m];
+            fmatrix = new double[n+m, m];
+            if( k>0 )
+            {
+                cmatrix = new double[k, m+1];
+            }
+            
+            //
+            // Fill design matrix, Y2, W2:
+            // * first N rows with basis functions for original points
+            // * next M rows with decay terms
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                
+                //
+                // prepare Ith row
+                // use Tmp for calculations to avoid multidimensional arrays overhead
+                //
+                for(j=0; j<=m-1; j++)
+                {
+                    if( j==0 )
+                    {
+                        tmp[j] = 1;
+                    }
+                    else
+                    {
+                        if( j==1 )
+                        {
+                            tmp[j] = x[i];
+                        }
+                        else
+                        {
+                            tmp[j] = 2*x[i]*tmp[j-1]-tmp[j-2];
+                        }
+                    }
+                }
+                for(i_=0; i_<=m-1;i_++)
+                {
+                    fmatrix[i,i_] = tmp[i_];
+                }
+            }
+            for(i=0; i<=m-1; i++)
+            {
+                for(j=0; j<=m-1; j++)
+                {
+                    if( i==j )
+                    {
+                        fmatrix[n+i,j] = decay;
+                    }
+                    else
+                    {
+                        fmatrix[n+i,j] = 0;
+                    }
+                }
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                y2[i_] = y[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                w2[i_] = w[i_];
+            }
+            mx = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                mx = mx+Math.Abs(w[i]);
+            }
+            mx = mx/n;
+            for(i=0; i<=m-1; i++)
+            {
+                y2[n+i] = 0;
+                w2[n+i] = mx;
+            }
+            
+            //
+            // fill constraints matrix
+            //
+            for(i=0; i<=k-1; i++)
+            {
+                
+                //
+                // prepare Ith row
+                // use Tmp for basis function values,
+                // TmpDiff for basos function derivatives
+                //
+                for(j=0; j<=m-1; j++)
+                {
+                    if( j==0 )
+                    {
+                        tmp[j] = 1;
+                        tmpdiff[j] = 0;
+                    }
+                    else
+                    {
+                        if( j==1 )
+                        {
+                            tmp[j] = xc[i];
+                            tmpdiff[j] = 1;
+                        }
+                        else
+                        {
+                            tmp[j] = 2*xc[i]*tmp[j-1]-tmp[j-2];
+                            tmpdiff[j] = 2*(tmp[j-1]+xc[i]*tmpdiff[j-1])-tmpdiff[j-2];
+                        }
+                    }
+                }
+                if( dc[i]==0 )
+                {
+                    for(i_=0; i_<=m-1;i_++)
+                    {
+                        cmatrix[i,i_] = tmp[i_];
+                    }
+                }
+                if( dc[i]==1 )
+                {
+                    for(i_=0; i_<=m-1;i_++)
+                    {
+                        cmatrix[i,i_] = tmpdiff[i_];
+                    }
+                }
+                cmatrix[i,m] = yc[i];
+            }
+            
+            //
+            // Solve constrained task
+            //
+            if( k>0 )
+            {
+                
+                //
+                // solve using regularization
+                //
+                lsfitlinearwc(y2, w2, fmatrix, cmatrix, n+m, m, k, ref info, ref c, rep);
+            }
+            else
+            {
+                
+                //
+                // no constraints, no regularization needed
+                //
+                lsfitlinearwc(y, w, fmatrix, cmatrix, n, m, 0, ref info, ref c, rep);
+            }
+            if( info<0 )
+            {
+                return;
+            }
+        }
+
+
+        /*************************************************************************
+        Internal Floater-Hormann fitting subroutine for fixed D
+        *************************************************************************/
+        private static void barycentricfitwcfixedd(double[] x,
+            double[] y,
+            double[] w,
+            int n,
+            double[] xc,
+            double[] yc,
+            int[] dc,
+            int k,
+            int m,
+            int d,
+            ref int info,
+            ratint.barycentricinterpolant b,
+            barycentricfitreport rep)
+        {
+            double[,] fmatrix = new double[0,0];
+            double[,] cmatrix = new double[0,0];
+            double[] y2 = new double[0];
+            double[] w2 = new double[0];
+            double[] sx = new double[0];
+            double[] sy = new double[0];
+            double[] sbf = new double[0];
+            double[] xoriginal = new double[0];
+            double[] yoriginal = new double[0];
+            double[] tmp = new double[0];
+            lsfitreport lrep = new lsfitreport();
+            double v0 = 0;
+            double v1 = 0;
+            double mx = 0;
+            ratint.barycentricinterpolant b2 = new ratint.barycentricinterpolant();
+            int i = 0;
+            int j = 0;
+            int relcnt = 0;
+            double xa = 0;
+            double xb = 0;
+            double sa = 0;
+            double sb = 0;
+            double decay = 0;
+            int i_ = 0;
+
+            x = (double[])x.Clone();
+            y = (double[])y.Clone();
+            w = (double[])w.Clone();
+            xc = (double[])xc.Clone();
+            yc = (double[])yc.Clone();
+            info = 0;
+
+            if( ((n<1 || m<2) || k<0) || k>=m )
+            {
+                info = -1;
+                return;
+            }
+            for(i=0; i<=k-1; i++)
+            {
+                info = 0;
+                if( dc[i]<0 )
+                {
+                    info = -1;
+                }
+                if( dc[i]>1 )
+                {
+                    info = -1;
+                }
+                if( info<0 )
+                {
+                    return;
+                }
+            }
+            
+            //
+            // weight decay for correct handling of task which becomes
+            // degenerate after constraints are applied
+            //
+            decay = 10000*math.machineepsilon;
+            
+            //
+            // Scale X, Y, XC, YC
+            //
+            lsfitscalexy(ref x, ref y, ref w, n, ref xc, ref yc, dc, k, ref xa, ref xb, ref sa, ref sb, ref xoriginal, ref yoriginal);
+            
+            //
+            // allocate space, initialize:
+            // * FMatrix-   values of basis functions at X[]
+            // * CMatrix-   values (derivatives) of basis functions at XC[]
+            //
+            y2 = new double[n+m];
+            w2 = new double[n+m];
+            fmatrix = new double[n+m, m];
+            if( k>0 )
+            {
+                cmatrix = new double[k, m+1];
+            }
+            y2 = new double[n+m];
+            w2 = new double[n+m];
+            
+            //
+            // Prepare design and constraints matrices:
+            // * fill constraints matrix
+            // * fill first N rows of design matrix with values
+            // * fill next M rows of design matrix with regularizing term
+            // * append M zeros to Y
+            // * append M elements, mean(abs(W)) each, to W
+            //
+            sx = new double[m];
+            sy = new double[m];
+            sbf = new double[m];
+            for(j=0; j<=m-1; j++)
+            {
+                sx[j] = (double)(2*j)/(double)(m-1)-1;
+            }
+            for(i=0; i<=m-1; i++)
+            {
+                sy[i] = 1;
+            }
+            ratint.barycentricbuildfloaterhormann(sx, sy, m, d, b2);
+            mx = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                barycentriccalcbasis(b2, x[i], ref sbf);
+                for(i_=0; i_<=m-1;i_++)
+                {
+                    fmatrix[i,i_] = sbf[i_];
+                }
+                y2[i] = y[i];
+                w2[i] = w[i];
+                mx = mx+Math.Abs(w[i])/n;
+            }
+            for(i=0; i<=m-1; i++)
+            {
+                for(j=0; j<=m-1; j++)
+                {
+                    if( i==j )
+                    {
+                        fmatrix[n+i,j] = decay;
+                    }
+                    else
+                    {
+                        fmatrix[n+i,j] = 0;
+                    }
+                }
+                y2[n+i] = 0;
+                w2[n+i] = mx;
+            }
+            if( k>0 )
+            {
+                for(j=0; j<=m-1; j++)
+                {
+                    for(i=0; i<=m-1; i++)
+                    {
+                        sy[i] = 0;
+                    }
+                    sy[j] = 1;
+                    ratint.barycentricbuildfloaterhormann(sx, sy, m, d, b2);
+                    for(i=0; i<=k-1; i++)
+                    {
+                        alglib.ap.assert(dc[i]>=0 && dc[i]<=1, "BarycentricFit: internal error!");
+                        ratint.barycentricdiff1(b2, xc[i], ref v0, ref v1);
+                        if( dc[i]==0 )
+                        {
+                            cmatrix[i,j] = v0;
+                        }
+                        if( dc[i]==1 )
+                        {
+                            cmatrix[i,j] = v1;
+                        }
+                    }
+                }
+                for(i=0; i<=k-1; i++)
+                {
+                    cmatrix[i,m] = yc[i];
+                }
+            }
+            
+            //
+            // Solve constrained task
+            //
+            if( k>0 )
+            {
+                
+                //
+                // solve using regularization
+                //
+                lsfitlinearwc(y2, w2, fmatrix, cmatrix, n+m, m, k, ref info, ref tmp, lrep);
+            }
+            else
+            {
+                
+                //
+                // no constraints, no regularization needed
+                //
+                lsfitlinearwc(y, w, fmatrix, cmatrix, n, m, k, ref info, ref tmp, lrep);
+            }
+            if( info<0 )
+            {
+                return;
+            }
+            
+            //
+            // Generate interpolant and scale it
+            //
+            for(i_=0; i_<=m-1;i_++)
+            {
+                sy[i_] = tmp[i_];
+            }
+            ratint.barycentricbuildfloaterhormann(sx, sy, m, d, b);
+            ratint.barycentriclintransx(b, 2/(xb-xa), -((xa+xb)/(xb-xa)));
+            ratint.barycentriclintransy(b, sb-sa, sa);
+            
+            //
+            // Scale absolute errors obtained from LSFitLinearW.
+            // Relative error should be calculated separately
+            // (because of shifting/scaling of the task)
+            //
+            rep.taskrcond = lrep.taskrcond;
+            rep.rmserror = lrep.rmserror*(sb-sa);
+            rep.avgerror = lrep.avgerror*(sb-sa);
+            rep.maxerror = lrep.maxerror*(sb-sa);
+            rep.avgrelerror = 0;
+            relcnt = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                if( (double)(yoriginal[i])!=(double)(0) )
+                {
+                    rep.avgrelerror = rep.avgrelerror+Math.Abs(ratint.barycentriccalc(b, xoriginal[i])-yoriginal[i])/Math.Abs(yoriginal[i]);
+                    relcnt = relcnt+1;
+                }
+            }
+            if( relcnt!=0 )
+            {
+                rep.avgrelerror = rep.avgrelerror/relcnt;
+            }
+        }
+
+
+    }
+    public class pspline
+    {
+        /*************************************************************************
+        Parametric spline inteprolant: 2-dimensional curve.
+
+        You should not try to access its members directly - use PSpline2XXXXXXXX()
+        functions instead.
+        *************************************************************************/
+        public class pspline2interpolant
+        {
+            public int n;
+            public bool periodic;
+            public double[] p;
+            public spline1d.spline1dinterpolant x;
+            public spline1d.spline1dinterpolant y;
+            public pspline2interpolant()
+            {
+                p = new double[0];
+                x = new spline1d.spline1dinterpolant();
+                y = new spline1d.spline1dinterpolant();
+            }
+        };
+
+
+        /*************************************************************************
+        Parametric spline inteprolant: 3-dimensional curve.
+
+        You should not try to access its members directly - use PSpline3XXXXXXXX()
+        functions instead.
+        *************************************************************************/
+        public class pspline3interpolant
+        {
+            public int n;
+            public bool periodic;
+            public double[] p;
+            public spline1d.spline1dinterpolant x;
+            public spline1d.spline1dinterpolant y;
+            public spline1d.spline1dinterpolant z;
+            public pspline3interpolant()
+            {
+                p = new double[0];
+                x = new spline1d.spline1dinterpolant();
+                y = new spline1d.spline1dinterpolant();
+                z = new spline1d.spline1dinterpolant();
+            }
+        };
+
+
+
+
+        /*************************************************************************
+        This function  builds  non-periodic 2-dimensional parametric spline  which
+        starts at (X[0],Y[0]) and ends at (X[N-1],Y[N-1]).
+
+        INPUT PARAMETERS:
+            XY  -   points, array[0..N-1,0..1].
+                    XY[I,0:1] corresponds to the Ith point.
+                    Order of points is important!
+            N   -   points count, N>=5 for Akima splines, N>=2 for other types  of
+                    splines.
+            ST  -   spline type:
+                    * 0     Akima spline
+                    * 1     parabolically terminated Catmull-Rom spline (Tension=0)
+                    * 2     parabolically terminated cubic spline
+            PT  -   parameterization type:
+                    * 0     uniform
+                    * 1     chord length
+                    * 2     centripetal
+
+        OUTPUT PARAMETERS:
+            P   -   parametric spline interpolant
+
+
+        NOTES:
+        * this function  assumes  that  there all consequent points  are distinct.
+          I.e. (x0,y0)<>(x1,y1),  (x1,y1)<>(x2,y2),  (x2,y2)<>(x3,y3)  and  so on.
+          However, non-consequent points may coincide, i.e. we can  have  (x0,y0)=
+          =(x2,y2).
+
+          -- ALGLIB PROJECT --
+             Copyright 28.05.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void pspline2build(double[,] xy,
+            int n,
+            int st,
+            int pt,
+            pspline2interpolant p)
+        {
+            double[] tmp = new double[0];
+            int i_ = 0;
+
+            xy = (double[,])xy.Clone();
+
+            alglib.ap.assert(st>=0 && st<=2, "PSpline2Build: incorrect spline type!");
+            alglib.ap.assert(pt>=0 && pt<=2, "PSpline2Build: incorrect parameterization type!");
+            if( st==0 )
+            {
+                alglib.ap.assert(n>=5, "PSpline2Build: N<5 (minimum value for Akima splines)!");
+            }
+            else
+            {
+                alglib.ap.assert(n>=2, "PSpline2Build: N<2!");
+            }
+            
+            //
+            // Prepare
+            //
+            p.n = n;
+            p.periodic = false;
+            tmp = new double[n];
+            
+            //
+            // Build parameterization, check that all parameters are distinct
+            //
+            pspline2par(xy, n, pt, ref p.p);
+            alglib.ap.assert(apserv.aredistinct(p.p, n), "PSpline2Build: consequent points are too close!");
+            
+            //
+            // Build splines
+            //
+            if( st==0 )
+            {
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    tmp[i_] = xy[i_,0];
+                }
+                spline1d.spline1dbuildakima(p.p, tmp, n, p.x);
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    tmp[i_] = xy[i_,1];
+                }
+                spline1d.spline1dbuildakima(p.p, tmp, n, p.y);
+            }
+            if( st==1 )
+            {
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    tmp[i_] = xy[i_,0];
+                }
+                spline1d.spline1dbuildcatmullrom(p.p, tmp, n, 0, 0.0, p.x);
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    tmp[i_] = xy[i_,1];
+                }
+                spline1d.spline1dbuildcatmullrom(p.p, tmp, n, 0, 0.0, p.y);
+            }
+            if( st==2 )
+            {
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    tmp[i_] = xy[i_,0];
+                }
+                spline1d.spline1dbuildcubic(p.p, tmp, n, 0, 0.0, 0, 0.0, p.x);
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    tmp[i_] = xy[i_,1];
+                }
+                spline1d.spline1dbuildcubic(p.p, tmp, n, 0, 0.0, 0, 0.0, p.y);
+            }
+        }
+
+
+        /*************************************************************************
+        This function  builds  non-periodic 3-dimensional parametric spline  which
+        starts at (X[0],Y[0],Z[0]) and ends at (X[N-1],Y[N-1],Z[N-1]).
+
+        Same as PSpline2Build() function, but for 3D, so we  won't  duplicate  its
+        description here.
+
+          -- ALGLIB PROJECT --
+             Copyright 28.05.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void pspline3build(double[,] xy,
+            int n,
+            int st,
+            int pt,
+            pspline3interpolant p)
+        {
+            double[] tmp = new double[0];
+            int i_ = 0;
+
+            xy = (double[,])xy.Clone();
+
+            alglib.ap.assert(st>=0 && st<=2, "PSpline3Build: incorrect spline type!");
+            alglib.ap.assert(pt>=0 && pt<=2, "PSpline3Build: incorrect parameterization type!");
+            if( st==0 )
+            {
+                alglib.ap.assert(n>=5, "PSpline3Build: N<5 (minimum value for Akima splines)!");
+            }
+            else
+            {
+                alglib.ap.assert(n>=2, "PSpline3Build: N<2!");
+            }
+            
+            //
+            // Prepare
+            //
+            p.n = n;
+            p.periodic = false;
+            tmp = new double[n];
+            
+            //
+            // Build parameterization, check that all parameters are distinct
+            //
+            pspline3par(xy, n, pt, ref p.p);
+            alglib.ap.assert(apserv.aredistinct(p.p, n), "PSpline3Build: consequent points are too close!");
+            
+            //
+            // Build splines
+            //
+            if( st==0 )
+            {
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    tmp[i_] = xy[i_,0];
+                }
+                spline1d.spline1dbuildakima(p.p, tmp, n, p.x);
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    tmp[i_] = xy[i_,1];
+                }
+                spline1d.spline1dbuildakima(p.p, tmp, n, p.y);
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    tmp[i_] = xy[i_,2];
+                }
+                spline1d.spline1dbuildakima(p.p, tmp, n, p.z);
+            }
+            if( st==1 )
+            {
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    tmp[i_] = xy[i_,0];
+                }
+                spline1d.spline1dbuildcatmullrom(p.p, tmp, n, 0, 0.0, p.x);
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    tmp[i_] = xy[i_,1];
+                }
+                spline1d.spline1dbuildcatmullrom(p.p, tmp, n, 0, 0.0, p.y);
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    tmp[i_] = xy[i_,2];
+                }
+                spline1d.spline1dbuildcatmullrom(p.p, tmp, n, 0, 0.0, p.z);
+            }
+            if( st==2 )
+            {
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    tmp[i_] = xy[i_,0];
+                }
+                spline1d.spline1dbuildcubic(p.p, tmp, n, 0, 0.0, 0, 0.0, p.x);
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    tmp[i_] = xy[i_,1];
+                }
+                spline1d.spline1dbuildcubic(p.p, tmp, n, 0, 0.0, 0, 0.0, p.y);
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    tmp[i_] = xy[i_,2];
+                }
+                spline1d.spline1dbuildcubic(p.p, tmp, n, 0, 0.0, 0, 0.0, p.z);
+            }
+        }
+
+
+        /*************************************************************************
+        This  function  builds  periodic  2-dimensional  parametric  spline  which
+        starts at (X[0],Y[0]), goes through all points to (X[N-1],Y[N-1]) and then
+        back to (X[0],Y[0]).
+
+        INPUT PARAMETERS:
+            XY  -   points, array[0..N-1,0..1].
+                    XY[I,0:1] corresponds to the Ith point.
+                    XY[N-1,0:1] must be different from XY[0,0:1].
+                    Order of points is important!
+            N   -   points count, N>=3 for other types of splines.
+            ST  -   spline type:
+                    * 1     Catmull-Rom spline (Tension=0) with cyclic boundary conditions
+                    * 2     cubic spline with cyclic boundary conditions
+            PT  -   parameterization type:
+                    * 0     uniform
+                    * 1     chord length
+                    * 2     centripetal
+
+        OUTPUT PARAMETERS:
+            P   -   parametric spline interpolant
+
+
+        NOTES:
+        * this function  assumes  that there all consequent points  are  distinct.
+          I.e. (x0,y0)<>(x1,y1), (x1,y1)<>(x2,y2),  (x2,y2)<>(x3,y3)  and  so  on.
+          However, non-consequent points may coincide, i.e. we can  have  (x0,y0)=
+          =(x2,y2).
+        * last point of sequence is NOT equal to the first  point.  You  shouldn't
+          make curve "explicitly periodic" by making them equal.
+
+          -- ALGLIB PROJECT --
+             Copyright 28.05.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void pspline2buildperiodic(double[,] xy,
+            int n,
+            int st,
+            int pt,
+            pspline2interpolant p)
+        {
+            double[,] xyp = new double[0,0];
+            double[] tmp = new double[0];
+            int i_ = 0;
+
+            xy = (double[,])xy.Clone();
+
+            alglib.ap.assert(st>=1 && st<=2, "PSpline2BuildPeriodic: incorrect spline type!");
+            alglib.ap.assert(pt>=0 && pt<=2, "PSpline2BuildPeriodic: incorrect parameterization type!");
+            alglib.ap.assert(n>=3, "PSpline2BuildPeriodic: N<3!");
+            
+            //
+            // Prepare
+            //
+            p.n = n;
+            p.periodic = true;
+            tmp = new double[n+1];
+            xyp = new double[n+1, 2];
+            for(i_=0; i_<=n-1;i_++)
+            {
+                xyp[i_,0] = xy[i_,0];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                xyp[i_,1] = xy[i_,1];
+            }
+            for(i_=0; i_<=1;i_++)
+            {
+                xyp[n,i_] = xy[0,i_];
+            }
+            
+            //
+            // Build parameterization, check that all parameters are distinct
+            //
+            pspline2par(xyp, n+1, pt, ref p.p);
+            alglib.ap.assert(apserv.aredistinct(p.p, n+1), "PSpline2BuildPeriodic: consequent (or first and last) points are too close!");
+            
+            //
+            // Build splines
+            //
+            if( st==1 )
+            {
+                for(i_=0; i_<=n;i_++)
+                {
+                    tmp[i_] = xyp[i_,0];
+                }
+                spline1d.spline1dbuildcatmullrom(p.p, tmp, n+1, -1, 0.0, p.x);
+                for(i_=0; i_<=n;i_++)
+                {
+                    tmp[i_] = xyp[i_,1];
+                }
+                spline1d.spline1dbuildcatmullrom(p.p, tmp, n+1, -1, 0.0, p.y);
+            }
+            if( st==2 )
+            {
+                for(i_=0; i_<=n;i_++)
+                {
+                    tmp[i_] = xyp[i_,0];
+                }
+                spline1d.spline1dbuildcubic(p.p, tmp, n+1, -1, 0.0, -1, 0.0, p.x);
+                for(i_=0; i_<=n;i_++)
+                {
+                    tmp[i_] = xyp[i_,1];
+                }
+                spline1d.spline1dbuildcubic(p.p, tmp, n+1, -1, 0.0, -1, 0.0, p.y);
+            }
+        }
+
+
+        /*************************************************************************
+        This  function  builds  periodic  3-dimensional  parametric  spline  which
+        starts at (X[0],Y[0],Z[0]), goes through all points to (X[N-1],Y[N-1],Z[N-1])
+        and then back to (X[0],Y[0],Z[0]).
+
+        Same as PSpline2Build() function, but for 3D, so we  won't  duplicate  its
+        description here.
+
+          -- ALGLIB PROJECT --
+             Copyright 28.05.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void pspline3buildperiodic(double[,] xy,
+            int n,
+            int st,
+            int pt,
+            pspline3interpolant p)
+        {
+            double[,] xyp = new double[0,0];
+            double[] tmp = new double[0];
+            int i_ = 0;
+
+            xy = (double[,])xy.Clone();
+
+            alglib.ap.assert(st>=1 && st<=2, "PSpline3BuildPeriodic: incorrect spline type!");
+            alglib.ap.assert(pt>=0 && pt<=2, "PSpline3BuildPeriodic: incorrect parameterization type!");
+            alglib.ap.assert(n>=3, "PSpline3BuildPeriodic: N<3!");
+            
+            //
+            // Prepare
+            //
+            p.n = n;
+            p.periodic = true;
+            tmp = new double[n+1];
+            xyp = new double[n+1, 3];
+            for(i_=0; i_<=n-1;i_++)
+            {
+                xyp[i_,0] = xy[i_,0];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                xyp[i_,1] = xy[i_,1];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                xyp[i_,2] = xy[i_,2];
+            }
+            for(i_=0; i_<=2;i_++)
+            {
+                xyp[n,i_] = xy[0,i_];
+            }
+            
+            //
+            // Build parameterization, check that all parameters are distinct
+            //
+            pspline3par(xyp, n+1, pt, ref p.p);
+            alglib.ap.assert(apserv.aredistinct(p.p, n+1), "PSplineBuild2Periodic: consequent (or first and last) points are too close!");
+            
+            //
+            // Build splines
+            //
+            if( st==1 )
+            {
+                for(i_=0; i_<=n;i_++)
+                {
+                    tmp[i_] = xyp[i_,0];
+                }
+                spline1d.spline1dbuildcatmullrom(p.p, tmp, n+1, -1, 0.0, p.x);
+                for(i_=0; i_<=n;i_++)
+                {
+                    tmp[i_] = xyp[i_,1];
+                }
+                spline1d.spline1dbuildcatmullrom(p.p, tmp, n+1, -1, 0.0, p.y);
+                for(i_=0; i_<=n;i_++)
+                {
+                    tmp[i_] = xyp[i_,2];
+                }
+                spline1d.spline1dbuildcatmullrom(p.p, tmp, n+1, -1, 0.0, p.z);
+            }
+            if( st==2 )
+            {
+                for(i_=0; i_<=n;i_++)
+                {
+                    tmp[i_] = xyp[i_,0];
+                }
+                spline1d.spline1dbuildcubic(p.p, tmp, n+1, -1, 0.0, -1, 0.0, p.x);
+                for(i_=0; i_<=n;i_++)
+                {
+                    tmp[i_] = xyp[i_,1];
+                }
+                spline1d.spline1dbuildcubic(p.p, tmp, n+1, -1, 0.0, -1, 0.0, p.y);
+                for(i_=0; i_<=n;i_++)
+                {
+                    tmp[i_] = xyp[i_,2];
+                }
+                spline1d.spline1dbuildcubic(p.p, tmp, n+1, -1, 0.0, -1, 0.0, p.z);
+            }
+        }
+
+
+        /*************************************************************************
+        This function returns vector of parameter values correspoding to points.
+
+        I.e. for P created from (X[0],Y[0])...(X[N-1],Y[N-1]) and U=TValues(P)  we
+        have
+            (X[0],Y[0]) = PSpline2Calc(P,U[0]),
+            (X[1],Y[1]) = PSpline2Calc(P,U[1]),
+            (X[2],Y[2]) = PSpline2Calc(P,U[2]),
+            ...
+
+        INPUT PARAMETERS:
+            P   -   parametric spline interpolant
+
+        OUTPUT PARAMETERS:
+            N   -   array size
+            T   -   array[0..N-1]
+
+
+        NOTES:
+        * for non-periodic splines U[0]=0, U[0]<U[1]<...<U[N-1], U[N-1]=1
+        * for periodic splines     U[0]=0, U[0]<U[1]<...<U[N-1], U[N-1]<1
+
+          -- ALGLIB PROJECT --
+             Copyright 28.05.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void pspline2parametervalues(pspline2interpolant p,
+            ref int n,
+            ref double[] t)
+        {
+            int i_ = 0;
+
+            n = 0;
+            t = new double[0];
+
+            alglib.ap.assert(p.n>=2, "PSpline2ParameterValues: internal error!");
+            n = p.n;
+            t = new double[n];
+            for(i_=0; i_<=n-1;i_++)
+            {
+                t[i_] = p.p[i_];
+            }
+            t[0] = 0;
+            if( !p.periodic )
+            {
+                t[n-1] = 1;
+            }
+        }
+
+
+        /*************************************************************************
+        This function returns vector of parameter values correspoding to points.
+
+        Same as PSpline2ParameterValues(), but for 3D.
+
+          -- ALGLIB PROJECT --
+             Copyright 28.05.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void pspline3parametervalues(pspline3interpolant p,
+            ref int n,
+            ref double[] t)
+        {
+            int i_ = 0;
+
+            n = 0;
+            t = new double[0];
+
+            alglib.ap.assert(p.n>=2, "PSpline3ParameterValues: internal error!");
+            n = p.n;
+            t = new double[n];
+            for(i_=0; i_<=n-1;i_++)
+            {
+                t[i_] = p.p[i_];
+            }
+            t[0] = 0;
+            if( !p.periodic )
+            {
+                t[n-1] = 1;
+            }
+        }
+
+
+        /*************************************************************************
+        This function  calculates  the value of the parametric spline for a  given
+        value of parameter T
+
+        INPUT PARAMETERS:
+            P   -   parametric spline interpolant
+            T   -   point:
+                    * T in [0,1] corresponds to interval spanned by points
+                    * for non-periodic splines T<0 (or T>1) correspond to parts of
+                      the curve before the first (after the last) point
+                    * for periodic splines T<0 (or T>1) are projected  into  [0,1]
+                      by making T=T-floor(T).
+
+        OUTPUT PARAMETERS:
+            X   -   X-position
+            Y   -   Y-position
+
+
+          -- ALGLIB PROJECT --
+             Copyright 28.05.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void pspline2calc(pspline2interpolant p,
+            double t,
+            ref double x,
+            ref double y)
+        {
+            x = 0;
+            y = 0;
+
+            if( p.periodic )
+            {
+                t = t-(int)Math.Floor(t);
+            }
+            x = spline1d.spline1dcalc(p.x, t);
+            y = spline1d.spline1dcalc(p.y, t);
+        }
+
+
+        /*************************************************************************
+        This function  calculates  the value of the parametric spline for a  given
+        value of parameter T.
+
+        INPUT PARAMETERS:
+            P   -   parametric spline interpolant
+            T   -   point:
+                    * T in [0,1] corresponds to interval spanned by points
+                    * for non-periodic splines T<0 (or T>1) correspond to parts of
+                      the curve before the first (after the last) point
+                    * for periodic splines T<0 (or T>1) are projected  into  [0,1]
+                      by making T=T-floor(T).
+
+        OUTPUT PARAMETERS:
+            X   -   X-position
+            Y   -   Y-position
+            Z   -   Z-position
+
+
+          -- ALGLIB PROJECT --
+             Copyright 28.05.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void pspline3calc(pspline3interpolant p,
+            double t,
+            ref double x,
+            ref double y,
+            ref double z)
+        {
+            x = 0;
+            y = 0;
+            z = 0;
+
+            if( p.periodic )
+            {
+                t = t-(int)Math.Floor(t);
+            }
+            x = spline1d.spline1dcalc(p.x, t);
+            y = spline1d.spline1dcalc(p.y, t);
+            z = spline1d.spline1dcalc(p.z, t);
+        }
+
+
+        /*************************************************************************
+        This function  calculates  tangent vector for a given value of parameter T
+
+        INPUT PARAMETERS:
+            P   -   parametric spline interpolant
+            T   -   point:
+                    * T in [0,1] corresponds to interval spanned by points
+                    * for non-periodic splines T<0 (or T>1) correspond to parts of
+                      the curve before the first (after the last) point
+                    * for periodic splines T<0 (or T>1) are projected  into  [0,1]
+                      by making T=T-floor(T).
+
+        OUTPUT PARAMETERS:
+            X    -   X-component of tangent vector (normalized)
+            Y    -   Y-component of tangent vector (normalized)
+            
+        NOTE:
+            X^2+Y^2 is either 1 (for non-zero tangent vector) or 0.
+
+
+          -- ALGLIB PROJECT --
+             Copyright 28.05.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void pspline2tangent(pspline2interpolant p,
+            double t,
+            ref double x,
+            ref double y)
+        {
+            double v = 0;
+            double v0 = 0;
+            double v1 = 0;
+
+            x = 0;
+            y = 0;
+
+            if( p.periodic )
+            {
+                t = t-(int)Math.Floor(t);
+            }
+            pspline2diff(p, t, ref v0, ref x, ref v1, ref y);
+            if( (double)(x)!=(double)(0) || (double)(y)!=(double)(0) )
+            {
+                
+                //
+                // this code is a bit more complex than X^2+Y^2 to avoid
+                // overflow for large values of X and Y.
+                //
+                v = apserv.safepythag2(x, y);
+                x = x/v;
+                y = y/v;
+            }
+        }
+
+
+        /*************************************************************************
+        This function  calculates  tangent vector for a given value of parameter T
+
+        INPUT PARAMETERS:
+            P   -   parametric spline interpolant
+            T   -   point:
+                    * T in [0,1] corresponds to interval spanned by points
+                    * for non-periodic splines T<0 (or T>1) correspond to parts of
+                      the curve before the first (after the last) point
+                    * for periodic splines T<0 (or T>1) are projected  into  [0,1]
+                      by making T=T-floor(T).
+
+        OUTPUT PARAMETERS:
+            X    -   X-component of tangent vector (normalized)
+            Y    -   Y-component of tangent vector (normalized)
+            Z    -   Z-component of tangent vector (normalized)
+
+        NOTE:
+            X^2+Y^2+Z^2 is either 1 (for non-zero tangent vector) or 0.
+
+
+          -- ALGLIB PROJECT --
+             Copyright 28.05.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void pspline3tangent(pspline3interpolant p,
+            double t,
+            ref double x,
+            ref double y,
+            ref double z)
+        {
+            double v = 0;
+            double v0 = 0;
+            double v1 = 0;
+            double v2 = 0;
+
+            x = 0;
+            y = 0;
+            z = 0;
+
+            if( p.periodic )
+            {
+                t = t-(int)Math.Floor(t);
+            }
+            pspline3diff(p, t, ref v0, ref x, ref v1, ref y, ref v2, ref z);
+            if( ((double)(x)!=(double)(0) || (double)(y)!=(double)(0)) || (double)(z)!=(double)(0) )
+            {
+                v = apserv.safepythag3(x, y, z);
+                x = x/v;
+                y = y/v;
+                z = z/v;
+            }
+        }
+
+
+        /*************************************************************************
+        This function calculates derivative, i.e. it returns (dX/dT,dY/dT).
+
+        INPUT PARAMETERS:
+            P   -   parametric spline interpolant
+            T   -   point:
+                    * T in [0,1] corresponds to interval spanned by points
+                    * for non-periodic splines T<0 (or T>1) correspond to parts of
+                      the curve before the first (after the last) point
+                    * for periodic splines T<0 (or T>1) are projected  into  [0,1]
+                      by making T=T-floor(T).
+
+        OUTPUT PARAMETERS:
+            X   -   X-value
+            DX  -   X-derivative
+            Y   -   Y-value
+            DY  -   Y-derivative
+
+
+          -- ALGLIB PROJECT --
+             Copyright 28.05.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void pspline2diff(pspline2interpolant p,
+            double t,
+            ref double x,
+            ref double dx,
+            ref double y,
+            ref double dy)
+        {
+            double d2s = 0;
+
+            x = 0;
+            dx = 0;
+            y = 0;
+            dy = 0;
+
+            if( p.periodic )
+            {
+                t = t-(int)Math.Floor(t);
+            }
+            spline1d.spline1ddiff(p.x, t, ref x, ref dx, ref d2s);
+            spline1d.spline1ddiff(p.y, t, ref y, ref dy, ref d2s);
+        }
+
+
+        /*************************************************************************
+        This function calculates derivative, i.e. it returns (dX/dT,dY/dT,dZ/dT).
+
+        INPUT PARAMETERS:
+            P   -   parametric spline interpolant
+            T   -   point:
+                    * T in [0,1] corresponds to interval spanned by points
+                    * for non-periodic splines T<0 (or T>1) correspond to parts of
+                      the curve before the first (after the last) point
+                    * for periodic splines T<0 (or T>1) are projected  into  [0,1]
+                      by making T=T-floor(T).
+
+        OUTPUT PARAMETERS:
+            X   -   X-value
+            DX  -   X-derivative
+            Y   -   Y-value
+            DY  -   Y-derivative
+            Z   -   Z-value
+            DZ  -   Z-derivative
+
+
+          -- ALGLIB PROJECT --
+             Copyright 28.05.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void pspline3diff(pspline3interpolant p,
+            double t,
+            ref double x,
+            ref double dx,
+            ref double y,
+            ref double dy,
+            ref double z,
+            ref double dz)
+        {
+            double d2s = 0;
+
+            x = 0;
+            dx = 0;
+            y = 0;
+            dy = 0;
+            z = 0;
+            dz = 0;
+
+            if( p.periodic )
+            {
+                t = t-(int)Math.Floor(t);
+            }
+            spline1d.spline1ddiff(p.x, t, ref x, ref dx, ref d2s);
+            spline1d.spline1ddiff(p.y, t, ref y, ref dy, ref d2s);
+            spline1d.spline1ddiff(p.z, t, ref z, ref dz, ref d2s);
+        }
+
+
+        /*************************************************************************
+        This function calculates first and second derivative with respect to T.
+
+        INPUT PARAMETERS:
+            P   -   parametric spline interpolant
+            T   -   point:
+                    * T in [0,1] corresponds to interval spanned by points
+                    * for non-periodic splines T<0 (or T>1) correspond to parts of
+                      the curve before the first (after the last) point
+                    * for periodic splines T<0 (or T>1) are projected  into  [0,1]
+                      by making T=T-floor(T).
+
+        OUTPUT PARAMETERS:
+            X   -   X-value
+            DX  -   derivative
+            D2X -   second derivative
+            Y   -   Y-value
+            DY  -   derivative
+            D2Y -   second derivative
+
+
+          -- ALGLIB PROJECT --
+             Copyright 28.05.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void pspline2diff2(pspline2interpolant p,
+            double t,
+            ref double x,
+            ref double dx,
+            ref double d2x,
+            ref double y,
+            ref double dy,
+            ref double d2y)
+        {
+            x = 0;
+            dx = 0;
+            d2x = 0;
+            y = 0;
+            dy = 0;
+            d2y = 0;
+
+            if( p.periodic )
+            {
+                t = t-(int)Math.Floor(t);
+            }
+            spline1d.spline1ddiff(p.x, t, ref x, ref dx, ref d2x);
+            spline1d.spline1ddiff(p.y, t, ref y, ref dy, ref d2y);
+        }
+
+
+        /*************************************************************************
+        This function calculates first and second derivative with respect to T.
+
+        INPUT PARAMETERS:
+            P   -   parametric spline interpolant
+            T   -   point:
+                    * T in [0,1] corresponds to interval spanned by points
+                    * for non-periodic splines T<0 (or T>1) correspond to parts of
+                      the curve before the first (after the last) point
+                    * for periodic splines T<0 (or T>1) are projected  into  [0,1]
+                      by making T=T-floor(T).
+
+        OUTPUT PARAMETERS:
+            X   -   X-value
+            DX  -   derivative
+            D2X -   second derivative
+            Y   -   Y-value
+            DY  -   derivative
+            D2Y -   second derivative
+            Z   -   Z-value
+            DZ  -   derivative
+            D2Z -   second derivative
+
+
+          -- ALGLIB PROJECT --
+             Copyright 28.05.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void pspline3diff2(pspline3interpolant p,
+            double t,
+            ref double x,
+            ref double dx,
+            ref double d2x,
+            ref double y,
+            ref double dy,
+            ref double d2y,
+            ref double z,
+            ref double dz,
+            ref double d2z)
+        {
+            x = 0;
+            dx = 0;
+            d2x = 0;
+            y = 0;
+            dy = 0;
+            d2y = 0;
+            z = 0;
+            dz = 0;
+            d2z = 0;
+
+            if( p.periodic )
+            {
+                t = t-(int)Math.Floor(t);
+            }
+            spline1d.spline1ddiff(p.x, t, ref x, ref dx, ref d2x);
+            spline1d.spline1ddiff(p.y, t, ref y, ref dy, ref d2y);
+            spline1d.spline1ddiff(p.z, t, ref z, ref dz, ref d2z);
+        }
+
+
+        /*************************************************************************
+        This function  calculates  arc length, i.e. length of  curve  between  t=a
+        and t=b.
+
+        INPUT PARAMETERS:
+            P   -   parametric spline interpolant
+            A,B -   parameter values corresponding to arc ends:
+                    * B>A will result in positive length returned
+                    * B<A will result in negative length returned
+
+        RESULT:
+            length of arc starting at T=A and ending at T=B.
+
+
+          -- ALGLIB PROJECT --
+             Copyright 30.05.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static double pspline2arclength(pspline2interpolant p,
+            double a,
+            double b)
+        {
+            double result = 0;
+            autogk.autogkstate state = new autogk.autogkstate();
+            autogk.autogkreport rep = new autogk.autogkreport();
+            double sx = 0;
+            double dsx = 0;
+            double d2sx = 0;
+            double sy = 0;
+            double dsy = 0;
+            double d2sy = 0;
+
+            autogk.autogksmooth(a, b, state);
+            while( autogk.autogkiteration(state) )
+            {
+                spline1d.spline1ddiff(p.x, state.x, ref sx, ref dsx, ref d2sx);
+                spline1d.spline1ddiff(p.y, state.x, ref sy, ref dsy, ref d2sy);
+                state.f = apserv.safepythag2(dsx, dsy);
+            }
+            autogk.autogkresults(state, ref result, rep);
+            alglib.ap.assert(rep.terminationtype>0, "PSpline2ArcLength: internal error!");
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function  calculates  arc length, i.e. length of  curve  between  t=a
+        and t=b.
+
+        INPUT PARAMETERS:
+            P   -   parametric spline interpolant
+            A,B -   parameter values corresponding to arc ends:
+                    * B>A will result in positive length returned
+                    * B<A will result in negative length returned
+
+        RESULT:
+            length of arc starting at T=A and ending at T=B.
+
+
+          -- ALGLIB PROJECT --
+             Copyright 30.05.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static double pspline3arclength(pspline3interpolant p,
+            double a,
+            double b)
+        {
+            double result = 0;
+            autogk.autogkstate state = new autogk.autogkstate();
+            autogk.autogkreport rep = new autogk.autogkreport();
+            double sx = 0;
+            double dsx = 0;
+            double d2sx = 0;
+            double sy = 0;
+            double dsy = 0;
+            double d2sy = 0;
+            double sz = 0;
+            double dsz = 0;
+            double d2sz = 0;
+
+            autogk.autogksmooth(a, b, state);
+            while( autogk.autogkiteration(state) )
+            {
+                spline1d.spline1ddiff(p.x, state.x, ref sx, ref dsx, ref d2sx);
+                spline1d.spline1ddiff(p.y, state.x, ref sy, ref dsy, ref d2sy);
+                spline1d.spline1ddiff(p.z, state.x, ref sz, ref dsz, ref d2sz);
+                state.f = apserv.safepythag3(dsx, dsy, dsz);
+            }
+            autogk.autogkresults(state, ref result, rep);
+            alglib.ap.assert(rep.terminationtype>0, "PSpline3ArcLength: internal error!");
+            return result;
+        }
+
+
+        /*************************************************************************
+        Builds non-periodic parameterization for 2-dimensional spline
+        *************************************************************************/
+        private static void pspline2par(double[,] xy,
+            int n,
+            int pt,
+            ref double[] p)
+        {
+            double v = 0;
+            int i = 0;
+            int i_ = 0;
+
+            p = new double[0];
+
+            alglib.ap.assert(pt>=0 && pt<=2, "PSpline2Par: internal error!");
+            
+            //
+            // Build parameterization:
+            // * fill by non-normalized values
+            // * normalize them so we have P[0]=0, P[N-1]=1.
+            //
+            p = new double[n];
+            if( pt==0 )
+            {
+                for(i=0; i<=n-1; i++)
+                {
+                    p[i] = i;
+                }
+            }
+            if( pt==1 )
+            {
+                p[0] = 0;
+                for(i=1; i<=n-1; i++)
+                {
+                    p[i] = p[i-1]+apserv.safepythag2(xy[i,0]-xy[i-1,0], xy[i,1]-xy[i-1,1]);
+                }
+            }
+            if( pt==2 )
+            {
+                p[0] = 0;
+                for(i=1; i<=n-1; i++)
+                {
+                    p[i] = p[i-1]+Math.Sqrt(apserv.safepythag2(xy[i,0]-xy[i-1,0], xy[i,1]-xy[i-1,1]));
+                }
+            }
+            v = 1/p[n-1];
+            for(i_=0; i_<=n-1;i_++)
+            {
+                p[i_] = v*p[i_];
+            }
+        }
+
+
+        /*************************************************************************
+        Builds non-periodic parameterization for 3-dimensional spline
+        *************************************************************************/
+        private static void pspline3par(double[,] xy,
+            int n,
+            int pt,
+            ref double[] p)
+        {
+            double v = 0;
+            int i = 0;
+            int i_ = 0;
+
+            p = new double[0];
+
+            alglib.ap.assert(pt>=0 && pt<=2, "PSpline3Par: internal error!");
+            
+            //
+            // Build parameterization:
+            // * fill by non-normalized values
+            // * normalize them so we have P[0]=0, P[N-1]=1.
+            //
+            p = new double[n];
+            if( pt==0 )
+            {
+                for(i=0; i<=n-1; i++)
+                {
+                    p[i] = i;
+                }
+            }
+            if( pt==1 )
+            {
+                p[0] = 0;
+                for(i=1; i<=n-1; i++)
+                {
+                    p[i] = p[i-1]+apserv.safepythag3(xy[i,0]-xy[i-1,0], xy[i,1]-xy[i-1,1], xy[i,2]-xy[i-1,2]);
+                }
+            }
+            if( pt==2 )
+            {
+                p[0] = 0;
+                for(i=1; i<=n-1; i++)
+                {
+                    p[i] = p[i-1]+Math.Sqrt(apserv.safepythag3(xy[i,0]-xy[i-1,0], xy[i,1]-xy[i-1,1], xy[i,2]-xy[i-1,2]));
+                }
+            }
+            v = 1/p[n-1];
+            for(i_=0; i_<=n-1;i_++)
+            {
+                p[i_] = v*p[i_];
+            }
+        }
+
+
+    }
+    public class rbf
+    {
+        /*************************************************************************
+        RBF model.
+
+        Never try to directly work with fields of this object - always use  ALGLIB
+        functions to use this object.
+        *************************************************************************/
+        public class rbfmodel
+        {
+            public int ny;
+            public int nx;
+            public int nc;
+            public int nl;
+            public nearestneighbor.kdtree tree;
+            public double[,] xc;
+            public double[,] wr;
+            public double rmax;
+            public double[,] v;
+            public int gridtype;
+            public bool fixrad;
+            public double lambdav;
+            public double radvalue;
+            public double radzvalue;
+            public int nlayers;
+            public int aterm;
+            public int algorithmtype;
+            public double epsort;
+            public double epserr;
+            public int maxits;
+            public double h;
+            public int n;
+            public double[,] x;
+            public double[,] y;
+            public double[] calcbufxcx;
+            public double[,] calcbufx;
+            public int[] calcbuftags;
+            public rbfmodel()
+            {
+                tree = new nearestneighbor.kdtree();
+                xc = new double[0,0];
+                wr = new double[0,0];
+                v = new double[0,0];
+                x = new double[0,0];
+                y = new double[0,0];
+                calcbufxcx = new double[0];
+                calcbufx = new double[0,0];
+                calcbuftags = new int[0];
+            }
+        };
+
+
+        /*************************************************************************
+        RBF solution report:
+        * TerminationType   -   termination type, positive values - success,
+                                non-positive - failure.
+        *************************************************************************/
+        public class rbfreport
+        {
+            public int arows;
+            public int acols;
+            public int annz;
+            public int iterationscount;
+            public int nmv;
+            public int terminationtype;
+        };
+
+
+
+
+        public const double eps = 1.0E-6;
+        public const int mxnx = 3;
+        public const double rbffarradius = 6;
+        public const double rbfnearradius = 2.1;
+        public const double rbfmlradius = 3;
+        public const int rbffirstversion = 0;
+
+
+        /*************************************************************************
+        This function creates RBF  model  for  a  scalar (NY=1)  or  vector (NY>1)
+        function in a NX-dimensional space (NX=2 or NX=3).
+
+        Newly created model is empty. It can be used for interpolation right after
+        creation, but it just returns zeros. You have to add points to the  model,
+        tune interpolation settings, and then  call  model  construction  function
+        RBFBuildModel() which will update model according to your specification.
+
+        USAGE:
+        1. User creates model with RBFCreate()
+        2. User adds dataset with RBFSetPoints() (points do NOT have to  be  on  a
+           regular grid)
+        3. (OPTIONAL) User chooses polynomial term by calling:
+           * RBFLinTerm() to set linear term
+           * RBFConstTerm() to set constant term
+           * RBFZeroTerm() to set zero term
+           By default, linear term is used.
+        4. User chooses specific RBF algorithm to use: either QNN (RBFSetAlgoQNN)
+           or ML (RBFSetAlgoMultiLayer).
+        5. User calls RBFBuildModel() function which rebuilds model  according  to
+           the specification
+        6. User may call RBFCalc() to calculate model value at the specified point,
+           RBFGridCalc() to  calculate   model  values at the points of the regular
+           grid. User may extract model coefficients with RBFUnpack() call.
+           
+        INPUT PARAMETERS:
+            NX      -   dimension of the space, NX=2 or NX=3
+            NY      -   function dimension, NY>=1
+
+        OUTPUT PARAMETERS:
+            S       -   RBF model (initially equals to zero)
+
+        NOTE 1: memory requirements. RBF models require amount of memory  which is
+                proportional  to  the  number  of data points. Memory is allocated 
+                during model construction, but most of this memory is freed  after
+                model coefficients are calculated.
+                
+                Some approximate estimates for N centers with default settings are
+                given below:
+                * about 250*N*(sizeof(double)+2*sizeof(int)) bytes  of  memory  is
+                  needed during model construction stage.
+                * about 15*N*sizeof(double) bytes is needed after model is built.
+                For example, for N=100000 we may need 0.6 GB of memory  to  build
+                model, but just about 0.012 GB to store it.
+
+          -- ALGLIB --
+             Copyright 13.12.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rbfcreate(int nx,
+            int ny,
+            rbfmodel s)
+        {
+            int i = 0;
+            int j = 0;
+
+            alglib.ap.assert(nx==2 || nx==3, "RBFCreate: NX<>2 and NX<>3");
+            alglib.ap.assert(ny>=1, "RBFCreate: NY<1");
+            s.nx = nx;
+            s.ny = ny;
+            s.nl = 0;
+            s.nc = 0;
+            s.v = new double[ny, mxnx+1];
+            for(i=0; i<=ny-1; i++)
+            {
+                for(j=0; j<=mxnx; j++)
+                {
+                    s.v[i,j] = 0;
+                }
+            }
+            s.n = 0;
+            s.rmax = 0;
+            s.gridtype = 2;
+            s.fixrad = false;
+            s.radvalue = 1;
+            s.radzvalue = 5;
+            s.aterm = 1;
+            s.algorithmtype = 1;
+            
+            //
+            // stopping criteria
+            //
+            s.epsort = eps;
+            s.epserr = eps;
+            s.maxits = 0;
+        }
+
+
+        /*************************************************************************
+        This function adds dataset.
+
+        This function overrides results of the previous calls, i.e. multiple calls
+        of this function will result in only the last set being added.
+
+        INPUT PARAMETERS:
+            S       -   RBF model, initialized by RBFCreate() call.
+            XY      -   points, array[N,NX+NY]. One row corresponds to  one  point
+                        in the dataset. First NX elements  are  coordinates,  next
+                        NY elements are function values. Array may  be larger than 
+                        specific,  in  this  case  only leading [N,NX+NY] elements 
+                        will be used.
+            N       -   number of points in the dataset
+
+        After you've added dataset and (optionally) tuned algorithm  settings  you
+        should call RBFBuildModel() in order to build a model for you.
+
+        NOTE: this   function  has   some   serialization-related  subtleties.  We
+              recommend you to study serialization examples from ALGLIB  Reference
+              Manual if you want to perform serialization of your models.
+              
+
+          -- ALGLIB --
+             Copyright 13.12.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rbfsetpoints(rbfmodel s,
+            double[,] xy,
+            int n)
+        {
+            int i = 0;
+            int j = 0;
+
+            alglib.ap.assert(n>0, "RBFSetPoints: N<0");
+            alglib.ap.assert(alglib.ap.rows(xy)>=n, "RBFSetPoints: Rows(XY)<N");
+            alglib.ap.assert(alglib.ap.cols(xy)>=s.nx+s.ny, "RBFSetPoints: Cols(XY)<NX+NY");
+            s.n = n;
+            s.x = new double[s.n, mxnx];
+            s.y = new double[s.n, s.ny];
+            for(i=0; i<=s.n-1; i++)
+            {
+                for(j=0; j<=mxnx-1; j++)
+                {
+                    s.x[i,j] = 0;
+                }
+                for(j=0; j<=s.nx-1; j++)
+                {
+                    s.x[i,j] = xy[i,j];
+                }
+                for(j=0; j<=s.ny-1; j++)
+                {
+                    s.y[i,j] = xy[i,j+s.nx];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This  function  sets  RBF interpolation algorithm. ALGLIB supports several
+        RBF algorithms with different properties.
+
+        This algorithm is called RBF-QNN and  it  is  good  for  point  sets  with
+        following properties:
+        a) all points are distinct
+        b) all points are well separated.
+        c) points  distribution  is  approximately  uniform.  There is no "contour
+           lines", clusters of points, or other small-scale structures.
+
+        Algorithm description:
+        1) interpolation centers are allocated to data points
+        2) interpolation radii are calculated as distances to the  nearest centers
+           times Q coefficient (where Q is a value from [0.75,1.50]).
+        3) after  performing (2) radii are transformed in order to avoid situation
+           when single outlier has very large radius and  influences  many  points
+           across all dataset. Transformation has following form:
+               new_r[i] = min(r[i],Z*median(r[]))
+           where r[i] is I-th radius, median()  is a median  radius across  entire
+           dataset, Z is user-specified value which controls amount  of  deviation
+           from median radius.
+
+        When (a) is violated,  we  will  be unable to build RBF model. When (b) or
+        (c) are violated, model will be built, but interpolation quality  will  be
+        low. See http://www.alglib.net/interpolation/ for more information on this
+        subject.
+
+        This algorithm is used by default.
+
+        Additional Q parameter controls smoothness properties of the RBF basis:
+        * Q<0.75 will give perfectly conditioned basis,  but  terrible  smoothness
+          properties (RBF interpolant will have sharp peaks around function values)
+        * Q around 1.0 gives good balance between smoothness and condition number
+        * Q>1.5 will lead to badly conditioned systems and slow convergence of the
+          underlying linear solver (although smoothness will be very good)
+        * Q>2.0 will effectively make optimizer useless because it won't  converge
+          within reasonable amount of iterations. It is possible to set such large
+          Q, but it is advised not to do so.
+
+        INPUT PARAMETERS:
+            S       -   RBF model, initialized by RBFCreate() call
+            Q       -   Q parameter, Q>0, recommended value - 1.0
+            Z       -   Z parameter, Z>0, recommended value - 5.0
+
+        NOTE: this   function  has   some   serialization-related  subtleties.  We
+              recommend you to study serialization examples from ALGLIB  Reference
+              Manual if you want to perform serialization of your models.
+
+
+          -- ALGLIB --
+             Copyright 13.12.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rbfsetalgoqnn(rbfmodel s,
+            double q,
+            double z)
+        {
+            alglib.ap.assert(math.isfinite(q), "RBFSetAlgoQNN: Q is infinite or NAN");
+            alglib.ap.assert((double)(q)>(double)(0), "RBFSetAlgoQNN: Q<=0");
+            rbfgridpoints(s);
+            rbfradnn(s, q, z);
+            s.algorithmtype = 1;
+        }
+
+
+        /*************************************************************************
+        This  function  sets  RBF interpolation algorithm. ALGLIB supports several
+        RBF algorithms with different properties.
+
+        This  algorithm is called RBF-ML. It builds  multilayer  RBF  model,  i.e.
+        model with subsequently decreasing  radii,  which  allows  us  to  combine
+        smoothness (due to  large radii of  the first layers) with  exactness (due
+        to small radii of the last layers) and fast convergence.
+
+        Internally RBF-ML uses many different  means  of acceleration, from sparse
+        matrices  to  KD-trees,  which  results in algorithm whose working time is
+        roughly proportional to N*log(N)*Density*RBase^2*NLayers,  where  N  is  a
+        number of points, Density is an average density if points per unit of  the
+        interpolation space, RBase is an initial radius, NLayers is  a  number  of
+        layers.
+
+        RBF-ML is good for following kinds of interpolation problems:
+        1. "exact" problems (perfect fit) with well separated points
+        2. least squares problems with arbitrary distribution of points (algorithm
+           gives  perfect  fit  where it is possible, and resorts to least squares
+           fit in the hard areas).
+        3. noisy problems where  we  want  to  apply  some  controlled  amount  of
+           smoothing.
+
+        INPUT PARAMETERS:
+            S       -   RBF model, initialized by RBFCreate() call
+            RBase   -   RBase parameter, RBase>0
+            NLayers -   NLayers parameter, NLayers>0, recommended value  to  start
+                        with - about 5.
+            LambdaV -   regularization value, can be useful when  solving  problem
+                        in the least squares sense.  Optimal  lambda  is  problem-
+                        dependent and require trial and error. In our  experience,
+                        good lambda can be as large as 0.1, and you can use  0.001
+                        as initial guess.
+                        Default  value  - 0.01, which is used when LambdaV is  not
+                        given.  You  can  specify  zero  value,  but  it  is   not
+                        recommended to do so.
+
+        TUNING ALGORITHM
+
+        In order to use this algorithm you have to choose three parameters:
+        * initial radius RBase
+        * number of layers in the model NLayers
+        * regularization coefficient LambdaV
+
+        Initial radius is easy to choose - you can pick any number  several  times
+        larger  than  the  average  distance between points. Algorithm won't break
+        down if you choose radius which is too large (model construction time will
+        increase, but model will be built correctly).
+
+        Choose such number of layers that RLast=RBase/2^(NLayers-1)  (radius  used
+        by  the  last  layer)  will  be  smaller than the typical distance between
+        points.  In  case  model  error  is  too large, you can increase number of
+        layers.  Having  more  layers  will make model construction and evaluation
+        proportionally slower, but it will allow you to have model which precisely
+        fits your data. From the other side, if you want to  suppress  noise,  you
+        can DECREASE number of layers to make your model less flexible.
+
+        Regularization coefficient LambdaV controls smoothness of  the  individual
+        models built for each layer. We recommend you to use default value in case
+        you don't want to tune this parameter,  because  having  non-zero  LambdaV
+        accelerates and stabilizes internal iterative algorithm. In case you  want
+        to suppress noise you can use  LambdaV  as  additional  parameter  (larger
+        value = more smoothness) to tune.
+
+        TYPICAL ERRORS
+
+        1. Using  initial  radius  which is too large. Memory requirements  of the
+           RBF-ML are roughly proportional to N*Density*RBase^2 (where Density  is
+           an average density of points per unit of the interpolation  space).  In
+           the extreme case of the very large RBase we will need O(N^2)  units  of
+           memory - and many layers in order to decrease radius to some reasonably
+           small value.
+
+        2. Using too small number of layers - RBF models with large radius are not
+           flexible enough to reproduce small variations in the  target  function.
+           You  need  many  layers  with  different radii, from large to small, in
+           order to have good model.
+
+        3. Using  initial  radius  which  is  too  small.  You will get model with
+           "holes" in the areas which are too far away from interpolation centers.
+           However, algorithm will work correctly (and quickly) in this case.
+
+        4. Using too many layers - you will get too large and too slow model. This
+           model  will  perfectly  reproduce  your function, but maybe you will be
+           able to achieve similar results with less layers (and less memory).
+           
+          -- ALGLIB --
+             Copyright 02.03.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rbfsetalgomultilayer(rbfmodel s,
+            double rbase,
+            int nlayers,
+            double lambdav)
+        {
+            alglib.ap.assert(math.isfinite(rbase), "RBFSetAlgoMultiLayer: RBase is infinite or NaN");
+            alglib.ap.assert((double)(rbase)>(double)(0), "RBFSetAlgoMultiLayer: RBase<=0");
+            alglib.ap.assert(nlayers>=0, "RBFSetAlgoMultiLayer: NLayers<0");
+            alglib.ap.assert(math.isfinite(lambdav), "RBFSetAlgoMultiLayer: LambdaV is infinite or NAN");
+            alglib.ap.assert((double)(lambdav)>=(double)(0), "RBFSetAlgoMultiLayer: LambdaV<0");
+            s.radvalue = rbase;
+            s.nlayers = nlayers;
+            s.algorithmtype = 2;
+            s.lambdav = lambdav;
+        }
+
+
+        /*************************************************************************
+        This function sets linear term (model is a sum of radial  basis  functions
+        plus linear polynomial). This function won't have effect until  next  call 
+        to RBFBuildModel().
+
+        INPUT PARAMETERS:
+            S       -   RBF model, initialized by RBFCreate() call
+
+        NOTE: this   function  has   some   serialization-related  subtleties.  We
+              recommend you to study serialization examples from ALGLIB  Reference
+              Manual if you want to perform serialization of your models.
+
+          -- ALGLIB --
+             Copyright 13.12.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rbfsetlinterm(rbfmodel s)
+        {
+            s.aterm = 1;
+        }
+
+
+        /*************************************************************************
+        This function sets constant term (model is a sum of radial basis functions
+        plus constant).  This  function  won't  have  effect  until  next  call to 
+        RBFBuildModel().
+
+        INPUT PARAMETERS:
+            S       -   RBF model, initialized by RBFCreate() call
+
+        NOTE: this   function  has   some   serialization-related  subtleties.  We
+              recommend you to study serialization examples from ALGLIB  Reference
+              Manual if you want to perform serialization of your models.
+
+          -- ALGLIB --
+             Copyright 13.12.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rbfsetconstterm(rbfmodel s)
+        {
+            s.aterm = 2;
+        }
+
+
+        /*************************************************************************
+        This  function  sets  zero  term (model is a sum of radial basis functions 
+        without polynomial term). This function won't have effect until next  call
+        to RBFBuildModel().
+
+        INPUT PARAMETERS:
+            S       -   RBF model, initialized by RBFCreate() call
+
+        NOTE: this   function  has   some   serialization-related  subtleties.  We
+              recommend you to study serialization examples from ALGLIB  Reference
+              Manual if you want to perform serialization of your models.
+
+          -- ALGLIB --
+             Copyright 13.12.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rbfsetzeroterm(rbfmodel s)
+        {
+            s.aterm = 3;
+        }
+
+
+        /*************************************************************************
+        This function sets stopping criteria of the underlying linear solver.
+
+        INPUT PARAMETERS:
+            S       -   RBF model, initialized by RBFCreate() call
+            EpsOrt  -   orthogonality stopping criterion, EpsOrt>=0. Algorithm will
+                        stop when ||A'*r||<=EpsOrt where A' is a transpose of  the 
+                        system matrix, r is a residual vector.
+                        Recommended value of EpsOrt is equal to 1E-6.
+                        This criterion will stop algorithm when we have "bad fit"
+                        situation, i.e. when we should stop in a point with large,
+                        nonzero residual.
+            EpsErr  -   residual stopping  criterion.  Algorithm  will  stop  when
+                        ||r||<=EpsErr*||b||, where r is a residual vector, b is  a
+                        right part of the system (function values).
+                        Recommended value of EpsErr is equal to 1E-3 or 1E-6.
+                        This  criterion  will  stop  algorithm  in  a  "good  fit" 
+                        situation when we have near-zero residual near the desired
+                        solution.
+            MaxIts  -   this criterion will stop algorithm after MaxIts iterations.
+                        It should be used for debugging purposes only!
+                        Zero MaxIts means that no limit is placed on the number of
+                        iterations.
+
+        We  recommend  to  set  moderate  non-zero  values   EpsOrt   and   EpsErr 
+        simultaneously. Values equal to 10E-6 are good to start with. In case  you
+        need high performance and do not need high precision ,  you  may  decrease
+        EpsErr down to 0.001. However, we do not recommend decreasing EpsOrt.
+
+        As for MaxIts, we recommend to leave it zero unless you know what you do.
+
+        NOTE: this   function  has   some   serialization-related  subtleties.  We
+              recommend you to study serialization examples from ALGLIB  Reference
+              Manual if you want to perform serialization of your models.
+
+          -- ALGLIB --
+             Copyright 13.12.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rbfsetcond(rbfmodel s,
+            double epsort,
+            double epserr,
+            int maxits)
+        {
+            alglib.ap.assert(math.isfinite(epsort) && (double)(epsort)>=(double)(0), "RBFSetCond: EpsOrt is negative, INF or NAN");
+            alglib.ap.assert(math.isfinite(epserr) && (double)(epserr)>=(double)(0), "RBFSetCond: EpsB is negative, INF or NAN");
+            alglib.ap.assert(maxits>=0, "RBFSetCond: MaxIts is negative");
+            if( ((double)(epsort)==(double)(0) && (double)(epserr)==(double)(0)) && maxits==0 )
+            {
+                s.epsort = eps;
+                s.epserr = eps;
+                s.maxits = 0;
+            }
+            else
+            {
+                s.epsort = epsort;
+                s.epserr = epserr;
+                s.maxits = maxits;
+            }
+        }
+
+
+        /*************************************************************************
+        This   function  builds  RBF  model  and  returns  report  (contains  some 
+        information which can be used for evaluation of the algorithm properties).
+
+        Call to this function modifies RBF model by calculating its centers/radii/
+        weights  and  saving  them  into  RBFModel  structure.  Initially RBFModel 
+        contain zero coefficients, but after call to this function  we  will  have
+        coefficients which were calculated in order to fit our dataset.
+
+        After you called this function you can call RBFCalc(),  RBFGridCalc()  and
+        other model calculation functions.
+
+        INPUT PARAMETERS:
+            S       -   RBF model, initialized by RBFCreate() call
+            Rep     -   report:
+                        * Rep.TerminationType:
+                          * -5 - non-distinct basis function centers were detected,
+                                 interpolation aborted
+                          * -4 - nonconvergence of the internal SVD solver
+                          *  1 - successful termination
+                        Fields are used for debugging purposes:
+                        * Rep.IterationsCount - iterations count of the LSQR solver
+                        * Rep.NMV - number of matrix-vector products
+                        * Rep.ARows - rows count for the system matrix
+                        * Rep.ACols - columns count for the system matrix
+                        * Rep.ANNZ - number of significantly non-zero elements
+                          (elements above some algorithm-determined threshold)
+
+        NOTE:  failure  to  build  model will leave current state of the structure
+        unchanged.
+
+          -- ALGLIB --
+             Copyright 13.12.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rbfbuildmodel(rbfmodel s,
+            rbfreport rep)
+        {
+            nearestneighbor.kdtree tree = new nearestneighbor.kdtree();
+            nearestneighbor.kdtree ctree = new nearestneighbor.kdtree();
+            double[] dist = new double[0];
+            double[] xcx = new double[0];
+            double[,] a = new double[0,0];
+            double[,] v = new double[0,0];
+            double[,] omega = new double[0,0];
+            double[] y = new double[0];
+            double[,] residualy = new double[0,0];
+            double[] radius = new double[0];
+            double[,] xc = new double[0,0];
+            double[] mnx = new double[0];
+            double[] mxx = new double[0];
+            double[] edge = new double[0];
+            int[] mxsteps = new int[0];
+            int nc = 0;
+            double rmax = 0;
+            int[] tags = new int[0];
+            int[] ctags = new int[0];
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int k2 = 0;
+            int snnz = 0;
+            double[] tmp0 = new double[0];
+            double[] tmp1 = new double[0];
+            int layerscnt = 0;
+
+            alglib.ap.assert(s.nx==2 || s.nx==3, "RBFBuildModel: S.NX<>2 or S.NX<>3!");
+            
+            //
+            // Quick exit when we have no points or if setted multilayer algorithm
+            // by user without layers.
+            //
+            if( s.n==0 || (s.algorithmtype==2 && s.nlayers==0) )
+            {
+                rep.terminationtype = 1;
+                rep.iterationscount = 0;
+                rep.nmv = 0;
+                rep.arows = 0;
+                rep.acols = 0;
+                nearestneighbor.kdtreebuildtagged(s.xc, tags, 0, mxnx, 0, 2, s.tree);
+                s.xc = new double[0, 0];
+                s.wr = new double[0, 0];
+                s.nc = 0;
+                s.rmax = 0;
+                s.v = new double[s.ny, mxnx+1];
+                for(i=0; i<=s.ny-1; i++)
+                {
+                    for(j=0; j<=mxnx; j++)
+                    {
+                        s.v[i,j] = 0;
+                    }
+                }
+                return;
+            }
+            
+            //
+            // General case, N>0
+            //
+            rep.annz = 0;
+            rep.iterationscount = 0;
+            rep.nmv = 0;
+            xcx = new double[mxnx];
+            
+            //
+            // First model in a sequence - linear model.
+            // Residuals from linear regression are stored in the ResidualY variable
+            // (used later to build RBF models).
+            //
+            residualy = new double[s.n, s.ny];
+            for(i=0; i<=s.n-1; i++)
+            {
+                for(j=0; j<=s.ny-1; j++)
+                {
+                    residualy[i,j] = s.y[i,j];
+                }
+            }
+            if( !buildlinearmodel(s.x, ref residualy, s.n, s.ny, s.aterm, ref v) )
+            {
+                rep.terminationtype = -5;
+                return;
+            }
+            
+            //
+            // Second model in a sequence - RBF term.
+            //
+            // NOTE: assignments below are not necessary, but without them
+            //       MSVC complains about unitialized variables.
+            //
+            nc = 0;
+            rmax = 0;
+            layerscnt = 0;
+            if( s.algorithmtype==1 )
+            {
+                
+                //
+                // Add RBF model.
+                // This model uses local KD-trees to speed-up nearest neighbor searches.
+                //
+                if( s.gridtype==1 )
+                {
+                    mxx = new double[s.nx];
+                    mnx = new double[s.nx];
+                    mxsteps = new int[s.nx];
+                    edge = new double[s.nx];
+                    for(i=0; i<=s.nx-1; i++)
+                    {
+                        mxx[i] = s.x[0,i];
+                        mnx[i] = s.x[0,i];
+                    }
+                    for(i=0; i<=s.n-1; i++)
+                    {
+                        for(j=0; j<=s.nx-1; j++)
+                        {
+                            if( (double)(mxx[j])<(double)(s.x[i,j]) )
+                            {
+                                mxx[j] = s.x[i,j];
+                            }
+                            if( (double)(mnx[j])>(double)(s.x[i,j]) )
+                            {
+                                mnx[j] = s.x[i,j];
+                            }
+                        }
+                    }
+                    for(i=0; i<=s.nx-1; i++)
+                    {
+                        mxsteps[i] = (int)((mxx[i]-mnx[i])/(2*s.h))+1;
+                        edge[i] = (mxx[i]+mnx[i])/2-s.h*mxsteps[i];
+                    }
+                    nc = 1;
+                    for(i=0; i<=s.nx-1; i++)
+                    {
+                        mxsteps[i] = 2*mxsteps[i]+1;
+                        nc = nc*mxsteps[i];
+                    }
+                    xc = new double[nc, mxnx];
+                    if( s.nx==2 )
+                    {
+                        for(i=0; i<=mxsteps[0]-1; i++)
+                        {
+                            for(j=0; j<=mxsteps[1]-1; j++)
+                            {
+                                for(k2=0; k2<=mxnx-1; k2++)
+                                {
+                                    xc[i*mxsteps[1]+j,k2] = 0;
+                                }
+                                xc[i*mxsteps[1]+j,0] = edge[0]+s.h*i;
+                                xc[i*mxsteps[1]+j,1] = edge[1]+s.h*j;
+                            }
+                        }
+                    }
+                    if( s.nx==3 )
+                    {
+                        for(i=0; i<=mxsteps[0]-1; i++)
+                        {
+                            for(j=0; j<=mxsteps[1]-1; j++)
+                            {
+                                for(k=0; k<=mxsteps[2]-1; k++)
+                                {
+                                    for(k2=0; k2<=mxnx-1; k2++)
+                                    {
+                                        xc[i*mxsteps[1]+j,k2] = 0;
+                                    }
+                                    xc[(i*mxsteps[1]+j)*mxsteps[2]+k,0] = edge[0]+s.h*i;
+                                    xc[(i*mxsteps[1]+j)*mxsteps[2]+k,1] = edge[1]+s.h*j;
+                                    xc[(i*mxsteps[1]+j)*mxsteps[2]+k,2] = edge[2]+s.h*k;
+                                }
+                            }
+                        }
+                    }
+                }
+                else
+                {
+                    if( s.gridtype==2 )
+                    {
+                        nc = s.n;
+                        xc = new double[nc, mxnx];
+                        for(i=0; i<=nc-1; i++)
+                        {
+                            for(j=0; j<=mxnx-1; j++)
+                            {
+                                xc[i,j] = s.x[i,j];
+                            }
+                        }
+                    }
+                    else
+                    {
+                        if( s.gridtype==3 )
+                        {
+                            nc = s.nc;
+                            xc = new double[nc, mxnx];
+                            for(i=0; i<=nc-1; i++)
+                            {
+                                for(j=0; j<=mxnx-1; j++)
+                                {
+                                    xc[i,j] = s.xc[i,j];
+                                }
+                            }
+                        }
+                        else
+                        {
+                            alglib.ap.assert(false, "RBFBuildModel: either S.GridType<1 or S.GridType>3!");
+                        }
+                    }
+                }
+                rmax = 0;
+                radius = new double[nc];
+                ctags = new int[nc];
+                for(i=0; i<=nc-1; i++)
+                {
+                    ctags[i] = i;
+                }
+                nearestneighbor.kdtreebuildtagged(xc, ctags, nc, mxnx, 0, 2, ctree);
+                if( s.fixrad )
+                {
+                    
+                    //
+                    // Fixed radius
+                    //
+                    for(i=0; i<=nc-1; i++)
+                    {
+                        radius[i] = s.radvalue;
+                    }
+                    rmax = radius[0];
+                }
+                else
+                {
+                    
+                    //
+                    // Dynamic radius
+                    //
+                    if( nc==0 )
+                    {
+                        rmax = 1;
+                    }
+                    else
+                    {
+                        if( nc==1 )
+                        {
+                            radius[0] = s.radvalue;
+                            rmax = radius[0];
+                        }
+                        else
+                        {
+                            
+                            //
+                            // NC>1, calculate radii using distances to nearest neigbors
+                            //
+                            for(i=0; i<=nc-1; i++)
+                            {
+                                for(j=0; j<=mxnx-1; j++)
+                                {
+                                    xcx[j] = xc[i,j];
+                                }
+                                if( nearestneighbor.kdtreequeryknn(ctree, xcx, 1, false)>0 )
+                                {
+                                    nearestneighbor.kdtreequeryresultsdistances(ctree, ref dist);
+                                    radius[i] = s.radvalue*dist[0];
+                                }
+                                else
+                                {
+                                    
+                                    //
+                                    // No neighbors found (it will happen when we have only one center).
+                                    // Initialize radius with default value.
+                                    //
+                                    radius[i] = 1.0;
+                                }
+                            }
+                            
+                            //
+                            // Apply filtering
+                            //
+                            apserv.rvectorsetlengthatleast(ref tmp0, nc);
+                            for(i=0; i<=nc-1; i++)
+                            {
+                                tmp0[i] = radius[i];
+                            }
+                            tsort.tagsortfast(ref tmp0, ref tmp1, nc);
+                            for(i=0; i<=nc-1; i++)
+                            {
+                                radius[i] = Math.Min(radius[i], s.radzvalue*tmp0[nc/2]);
+                            }
+                            
+                            //
+                            // Calculate RMax, check that all radii are non-zero
+                            //
+                            for(i=0; i<=nc-1; i++)
+                            {
+                                rmax = Math.Max(rmax, radius[i]);
+                            }
+                            for(i=0; i<=nc-1; i++)
+                            {
+                                if( (double)(radius[i])==(double)(0) )
+                                {
+                                    rep.terminationtype = -5;
+                                    return;
+                                }
+                            }
+                        }
+                    }
+                }
+                apserv.ivectorsetlengthatleast(ref tags, s.n);
+                for(i=0; i<=s.n-1; i++)
+                {
+                    tags[i] = i;
+                }
+                nearestneighbor.kdtreebuildtagged(s.x, tags, s.n, mxnx, 0, 2, tree);
+                buildrbfmodellsqr(s.x, ref residualy, xc, radius, s.n, nc, s.ny, tree, ctree, s.epsort, s.epserr, s.maxits, ref rep.annz, ref snnz, ref omega, ref rep.terminationtype, ref rep.iterationscount, ref rep.nmv);
+                layerscnt = 1;
+            }
+            else
+            {
+                if( s.algorithmtype==2 )
+                {
+                    rmax = s.radvalue;
+                    buildrbfmlayersmodellsqr(s.x, ref residualy, ref xc, s.radvalue, ref radius, s.n, ref nc, s.ny, s.nlayers, ctree, 1.0E-6, 1.0E-6, 50, s.lambdav, ref rep.annz, ref omega, ref rep.terminationtype, ref rep.iterationscount, ref rep.nmv);
+                    layerscnt = s.nlayers;
+                }
+                else
+                {
+                    alglib.ap.assert(false, "RBFBuildModel: internal error(AlgorithmType neither 1 nor 2)");
+                }
+            }
+            if( rep.terminationtype<=0 )
+            {
+                return;
+            }
+            
+            //
+            // Model is built
+            //
+            s.nc = nc/layerscnt;
+            s.rmax = rmax;
+            s.nl = layerscnt;
+            s.xc = new double[s.nc, mxnx];
+            s.wr = new double[s.nc, 1+s.nl*s.ny];
+            s.v = new double[s.ny, mxnx+1];
+            for(i=0; i<=s.nc-1; i++)
+            {
+                for(j=0; j<=mxnx-1; j++)
+                {
+                    s.xc[i,j] = xc[i,j];
+                }
+            }
+            apserv.ivectorsetlengthatleast(ref tags, s.nc);
+            for(i=0; i<=s.nc-1; i++)
+            {
+                tags[i] = i;
+            }
+            nearestneighbor.kdtreebuildtagged(s.xc, tags, s.nc, mxnx, 0, 2, s.tree);
+            for(i=0; i<=s.nc-1; i++)
+            {
+                s.wr[i,0] = radius[i];
+                for(k=0; k<=layerscnt-1; k++)
+                {
+                    for(j=0; j<=s.ny-1; j++)
+                    {
+                        s.wr[i,1+k*s.ny+j] = omega[k*s.nc+i,j];
+                    }
+                }
+            }
+            for(i=0; i<=s.ny-1; i++)
+            {
+                for(j=0; j<=mxnx; j++)
+                {
+                    s.v[i,j] = v[i,j];
+                }
+            }
+            rep.terminationtype = 1;
+            rep.arows = s.n;
+            rep.acols = s.nc;
+        }
+
+
+        /*************************************************************************
+        This function calculates values of the RBF model in the given point.
+
+        This function should be used when we have NY=1 (scalar function) and  NX=2
+        (2-dimensional space). If you have 3-dimensional space, use RBFCalc3(). If
+        you have general situation (NX-dimensional space, NY-dimensional function)
+        you should use general, less efficient implementation RBFCalc().
+
+        If  you  want  to  calculate  function  values  many times, consider using 
+        RBFGridCalc2(), which is far more efficient than many subsequent calls  to
+        RBFCalc2().
+
+        This function returns 0.0 when:
+        * model is not initialized
+        * NX<>2
+         *NY<>1
+
+        INPUT PARAMETERS:
+            S       -   RBF model
+            X0      -   first coordinate, finite number
+            X1      -   second coordinate, finite number
+
+        RESULT:
+            value of the model or 0.0 (as defined above)
+
+          -- ALGLIB --
+             Copyright 13.12.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static double rbfcalc2(rbfmodel s,
+            double x0,
+            double x1)
+        {
+            double result = 0;
+            int i = 0;
+            int j = 0;
+            int lx = 0;
+            int tg = 0;
+            double d2 = 0;
+            double t = 0;
+            double bfcur = 0;
+            double rcur = 0;
+
+            alglib.ap.assert(math.isfinite(x0), "RBFCalc2: invalid value for X0 (X0 is Inf)!");
+            alglib.ap.assert(math.isfinite(x1), "RBFCalc2: invalid value for X1 (X1 is Inf)!");
+            result = 0;
+            if( (s.ny!=1 || s.nx!=2) || s.nc==0 )
+            {
+                return result;
+            }
+            apserv.rvectorsetlengthatleast(ref s.calcbufxcx, mxnx);
+            for(i=0; i<=mxnx-1; i++)
+            {
+                s.calcbufxcx[i] = 0.0;
+            }
+            s.calcbufxcx[0] = x0;
+            s.calcbufxcx[1] = x1;
+            lx = nearestneighbor.kdtreequeryrnn(s.tree, s.calcbufxcx, s.rmax*rbffarradius, true);
+            nearestneighbor.kdtreequeryresultsx(s.tree, ref s.calcbufx);
+            nearestneighbor.kdtreequeryresultstags(s.tree, ref s.calcbuftags);
+            for(i=0; i<=lx-1; i++)
+            {
+                tg = s.calcbuftags[i];
+                d2 = math.sqr(x0-s.calcbufx[i,0])+math.sqr(x1-s.calcbufx[i,1]);
+                rcur = s.wr[tg,0];
+                bfcur = Math.Exp(-(d2/(rcur*rcur)));
+                for(j=0; j<=s.nl-1; j++)
+                {
+                    result = result+bfcur*s.wr[tg,1+j];
+                    rcur = 0.5*rcur;
+                    t = bfcur*bfcur;
+                    bfcur = t*t;
+                }
+            }
+            result = result+s.v[0,0]*x0+s.v[0,1]*x1+s.v[0,mxnx];
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function calculates values of the RBF model in the given point.
+
+        This function should be used when we have NY=1 (scalar function) and  NX=3
+        (3-dimensional space). If you have 2-dimensional space, use RBFCalc2(). If
+        you have general situation (NX-dimensional space, NY-dimensional function)
+        you should use general, less efficient implementation RBFCalc().
+
+        This function returns 0.0 when:
+        * model is not initialized
+        * NX<>3
+         *NY<>1
+
+        INPUT PARAMETERS:
+            S       -   RBF model
+            X0      -   first coordinate, finite number
+            X1      -   second coordinate, finite number
+            X2      -   third coordinate, finite number
+
+        RESULT:
+            value of the model or 0.0 (as defined above)
+
+          -- ALGLIB --
+             Copyright 13.12.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static double rbfcalc3(rbfmodel s,
+            double x0,
+            double x1,
+            double x2)
+        {
+            double result = 0;
+            int i = 0;
+            int j = 0;
+            int lx = 0;
+            int tg = 0;
+            double t = 0;
+            double rcur = 0;
+            double bf = 0;
+
+            alglib.ap.assert(math.isfinite(x0), "RBFCalc3: invalid value for X0 (X0 is Inf or NaN)!");
+            alglib.ap.assert(math.isfinite(x1), "RBFCalc3: invalid value for X1 (X1 is Inf or NaN)!");
+            alglib.ap.assert(math.isfinite(x2), "RBFCalc3: invalid value for X2 (X2 is Inf or NaN)!");
+            result = 0;
+            if( (s.ny!=1 || s.nx!=3) || s.nc==0 )
+            {
+                return result;
+            }
+            
+            //
+            // calculating value for F(X)
+            //
+            apserv.rvectorsetlengthatleast(ref s.calcbufxcx, mxnx);
+            for(i=0; i<=mxnx-1; i++)
+            {
+                s.calcbufxcx[i] = 0.0;
+            }
+            s.calcbufxcx[0] = x0;
+            s.calcbufxcx[1] = x1;
+            s.calcbufxcx[2] = x2;
+            lx = nearestneighbor.kdtreequeryrnn(s.tree, s.calcbufxcx, s.rmax*rbffarradius, true);
+            nearestneighbor.kdtreequeryresultsx(s.tree, ref s.calcbufx);
+            nearestneighbor.kdtreequeryresultstags(s.tree, ref s.calcbuftags);
+            for(i=0; i<=lx-1; i++)
+            {
+                tg = s.calcbuftags[i];
+                rcur = s.wr[tg,0];
+                bf = Math.Exp(-((math.sqr(x0-s.calcbufx[i,0])+math.sqr(x1-s.calcbufx[i,1])+math.sqr(x2-s.calcbufx[i,2]))/math.sqr(rcur)));
+                for(j=0; j<=s.nl-1; j++)
+                {
+                    result = result+bf*s.wr[tg,1+j];
+                    t = bf*bf;
+                    bf = t*t;
+                }
+            }
+            result = result+s.v[0,0]*x0+s.v[0,1]*x1+s.v[0,2]*x2+s.v[0,mxnx];
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function calculates values of the RBF model at the given point.
+
+        This is general function which can be used for arbitrary NX (dimension  of 
+        the space of arguments) and NY (dimension of the function itself). However
+        when  you  have  NY=1  you  may  find more convenient to use RBFCalc2() or 
+        RBFCalc3().
+
+        This function returns 0.0 when model is not initialized.
+
+        INPUT PARAMETERS:
+            S       -   RBF model
+            X       -   coordinates, array[NX].
+                        X may have more than NX elements, in this case only 
+                        leading NX will be used.
+
+        OUTPUT PARAMETERS:
+            Y       -   function value, array[NY]. Y is out-parameter and 
+                        reallocated after call to this function. In case you  want
+                        to reuse previously allocated Y, you may use RBFCalcBuf(),
+                        which reallocates Y only when it is too small.
+
+          -- ALGLIB --
+             Copyright 13.12.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rbfcalc(rbfmodel s,
+            double[] x,
+            ref double[] y)
+        {
+            y = new double[0];
+
+            alglib.ap.assert(alglib.ap.len(x)>=s.nx, "RBFCalc: Length(X)<NX");
+            alglib.ap.assert(apserv.isfinitevector(x, s.nx), "RBFCalc: X contains infinite or NaN values");
+            rbfcalcbuf(s, x, ref y);
+        }
+
+
+        /*************************************************************************
+        This function calculates values of the RBF model at the given point.
+
+        Same as RBFCalc(), but does not reallocate Y when in is large enough to 
+        store function values.
+
+        INPUT PARAMETERS:
+            S       -   RBF model
+            X       -   coordinates, array[NX].
+                        X may have more than NX elements, in this case only 
+                        leading NX will be used.
+            Y       -   possibly preallocated array
+
+        OUTPUT PARAMETERS:
+            Y       -   function value, array[NY]. Y is not reallocated when it
+                        is larger than NY.
+
+          -- ALGLIB --
+             Copyright 13.12.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rbfcalcbuf(rbfmodel s,
+            double[] x,
+            ref double[] y)
+        {
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int lx = 0;
+            int tg = 0;
+            double t = 0;
+            double rcur = 0;
+            double bf = 0;
+
+            alglib.ap.assert(alglib.ap.len(x)>=s.nx, "RBFCalcBuf: Length(X)<NX");
+            alglib.ap.assert(apserv.isfinitevector(x, s.nx), "RBFCalcBuf: X contains infinite or NaN values");
+            if( alglib.ap.len(y)<s.ny )
+            {
+                y = new double[s.ny];
+            }
+            for(i=0; i<=s.ny-1; i++)
+            {
+                y[i] = 0;
+            }
+            if( s.nc==0 )
+            {
+                return;
+            }
+            s.calcbufxcx = new double[mxnx];
+            for(i=0; i<=mxnx-1; i++)
+            {
+                s.calcbufxcx[i] = 0.0;
+            }
+            for(i=0; i<=s.nx-1; i++)
+            {
+                s.calcbufxcx[i] = x[i];
+            }
+            lx = nearestneighbor.kdtreequeryrnn(s.tree, s.calcbufxcx, s.rmax*rbffarradius, true);
+            nearestneighbor.kdtreequeryresultsx(s.tree, ref s.calcbufx);
+            nearestneighbor.kdtreequeryresultstags(s.tree, ref s.calcbuftags);
+            for(i=0; i<=s.ny-1; i++)
+            {
+                for(j=0; j<=lx-1; j++)
+                {
+                    tg = s.calcbuftags[j];
+                    rcur = s.wr[tg,0];
+                    bf = Math.Exp(-((math.sqr(s.calcbufxcx[0]-s.calcbufx[j,0])+math.sqr(s.calcbufxcx[1]-s.calcbufx[j,1])+math.sqr(s.calcbufxcx[2]-s.calcbufx[j,2]))/math.sqr(rcur)));
+                    for(k=0; k<=s.nl-1; k++)
+                    {
+                        y[i] = y[i]+bf*s.wr[tg,1+k*s.ny+i];
+                        t = bf*bf;
+                        bf = t*t;
+                    }
+                }
+                y[i] = y[i]+s.v[i,0]*s.calcbufxcx[0]+s.v[i,1]*s.calcbufxcx[1]+s.v[i,2]*s.calcbufxcx[2]+s.v[i,mxnx];
+            }
+        }
+
+
+        /*************************************************************************
+        This function calculates values of the RBF model at the regular grid.
+
+        Grid have N0*N1 points, with Point[I,J] = (X0[I], X1[J])
+
+        This function returns 0.0 when:
+        * model is not initialized
+        * NX<>2
+         *NY<>1
+
+        INPUT PARAMETERS:
+            S       -   RBF model
+            X0      -   array of grid nodes, first coordinates, array[N0]
+            N0      -   grid size (number of nodes) in the first dimension
+            X1      -   array of grid nodes, second coordinates, array[N1]
+            N1      -   grid size (number of nodes) in the second dimension
+
+        OUTPUT PARAMETERS:
+            Y       -   function values, array[N0,N1]. Y is out-variable and 
+                        is reallocated by this function.
+
+          -- ALGLIB --
+             Copyright 13.12.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rbfgridcalc2(rbfmodel s,
+            double[] x0,
+            int n0,
+            double[] x1,
+            int n1,
+            ref double[,] y)
+        {
+            double[] cpx0 = new double[0];
+            double[] cpx1 = new double[0];
+            int[] p01 = new int[0];
+            int[] p11 = new int[0];
+            int[] p2 = new int[0];
+            double rlimit = 0;
+            double xcnorm2 = 0;
+            int hp01 = 0;
+            double hcpx0 = 0;
+            double xc0 = 0;
+            double xc1 = 0;
+            double omega = 0;
+            double radius = 0;
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int d = 0;
+            int i00 = 0;
+            int i01 = 0;
+            int i10 = 0;
+            int i11 = 0;
+
+            y = new double[0,0];
+
+            alglib.ap.assert(n0>0, "RBFGridCalc2: invalid value for N0 (N0<=0)!");
+            alglib.ap.assert(n1>0, "RBFGridCalc2: invalid value for N1 (N1<=0)!");
+            alglib.ap.assert(alglib.ap.len(x0)>=n0, "RBFGridCalc2: Length(X0)<N0");
+            alglib.ap.assert(alglib.ap.len(x1)>=n1, "RBFGridCalc2: Length(X1)<N1");
+            alglib.ap.assert(apserv.isfinitevector(x0, n0), "RBFGridCalc2: X0 contains infinite or NaN values!");
+            alglib.ap.assert(apserv.isfinitevector(x1, n1), "RBFGridCalc2: X1 contains infinite or NaN values!");
+            y = new double[n0, n1];
+            for(i=0; i<=n0-1; i++)
+            {
+                for(j=0; j<=n1-1; j++)
+                {
+                    y[i,j] = 0;
+                }
+            }
+            if( (s.ny!=1 || s.nx!=2) || s.nc==0 )
+            {
+                return;
+            }
+            
+            //
+            //create and sort arrays
+            //
+            cpx0 = new double[n0];
+            for(i=0; i<=n0-1; i++)
+            {
+                cpx0[i] = x0[i];
+            }
+            tsort.tagsort(ref cpx0, n0, ref p01, ref p2);
+            cpx1 = new double[n1];
+            for(i=0; i<=n1-1; i++)
+            {
+                cpx1[i] = x1[i];
+            }
+            tsort.tagsort(ref cpx1, n1, ref p11, ref p2);
+            
+            //
+            //calculate function's value
+            //
+            for(i=0; i<=s.nc-1; i++)
+            {
+                radius = s.wr[i,0];
+                for(d=0; d<=s.nl-1; d++)
+                {
+                    omega = s.wr[i,1+d];
+                    rlimit = radius*rbffarradius;
+                    
+                    //
+                    //search lower and upper indexes
+                    //
+                    i00 = tsort.lowerbound(cpx0, n0, s.xc[i,0]-rlimit);
+                    i01 = tsort.upperbound(cpx0, n0, s.xc[i,0]+rlimit);
+                    i10 = tsort.lowerbound(cpx1, n1, s.xc[i,1]-rlimit);
+                    i11 = tsort.upperbound(cpx1, n1, s.xc[i,1]+rlimit);
+                    xc0 = s.xc[i,0];
+                    xc1 = s.xc[i,1];
+                    for(j=i00; j<=i01-1; j++)
+                    {
+                        hcpx0 = cpx0[j];
+                        hp01 = p01[j];
+                        for(k=i10; k<=i11-1; k++)
+                        {
+                            xcnorm2 = math.sqr(hcpx0-xc0)+math.sqr(cpx1[k]-xc1);
+                            if( (double)(xcnorm2)<=(double)(rlimit*rlimit) )
+                            {
+                                y[hp01,p11[k]] = y[hp01,p11[k]]+Math.Exp(-(xcnorm2/math.sqr(radius)))*omega;
+                            }
+                        }
+                    }
+                    radius = 0.5*radius;
+                }
+            }
+            
+            //
+            //add linear term
+            //
+            for(i=0; i<=n0-1; i++)
+            {
+                for(j=0; j<=n1-1; j++)
+                {
+                    y[i,j] = y[i,j]+s.v[0,0]*x0[i]+s.v[0,1]*x1[j]+s.v[0,mxnx];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This function "unpacks" RBF model by extracting its coefficients.
+
+        INPUT PARAMETERS:
+            S       -   RBF model
+
+        OUTPUT PARAMETERS:
+            NX      -   dimensionality of argument
+            NY      -   dimensionality of the target function
+            XWR     -   model information, array[NC,NX+NY+1].
+                        One row of the array corresponds to one basis function:
+                        * first NX columns  - coordinates of the center 
+                        * next NY columns   - weights, one per dimension of the 
+                                              function being modelled
+                        * last column       - radius, same for all dimensions of
+                                              the function being modelled
+            NC      -   number of the centers
+            V       -   polynomial  term , array[NY,NX+1]. One row per one 
+                        dimension of the function being modelled. First NX 
+                        elements are linear coefficients, V[NX] is equal to the 
+                        constant part.
+
+          -- ALGLIB --
+             Copyright 13.12.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rbfunpack(rbfmodel s,
+            ref int nx,
+            ref int ny,
+            ref double[,] xwr,
+            ref int nc,
+            ref double[,] v)
+        {
+            int i = 0;
+            int j = 0;
+            double rcur = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            nx = 0;
+            ny = 0;
+            xwr = new double[0,0];
+            nc = 0;
+            v = new double[0,0];
+
+            nx = s.nx;
+            ny = s.ny;
+            nc = s.nc;
+            
+            //
+            // Fill V
+            //
+            v = new double[s.ny, s.nx+1];
+            for(i=0; i<=s.ny-1; i++)
+            {
+                for(i_=0; i_<=s.nx-1;i_++)
+                {
+                    v[i,i_] = s.v[i,i_];
+                }
+                v[i,s.nx] = s.v[i,mxnx];
+            }
+            
+            //
+            // Fill XWR and V
+            //
+            if( nc*s.nl>0 )
+            {
+                xwr = new double[s.nc*s.nl, s.nx+s.ny+1];
+                for(i=0; i<=s.nc-1; i++)
+                {
+                    rcur = s.wr[i,0];
+                    for(j=0; j<=s.nl-1; j++)
+                    {
+                        for(i_=0; i_<=s.nx-1;i_++)
+                        {
+                            xwr[i*s.nl+j,i_] = s.xc[i,i_];
+                        }
+                        i1_ = (1+j*s.ny) - (s.nx);
+                        for(i_=s.nx; i_<=s.nx+s.ny-1;i_++)
+                        {
+                            xwr[i*s.nl+j,i_] = s.wr[i,i_+i1_];
+                        }
+                        xwr[i*s.nl+j,s.nx+s.ny] = rcur;
+                        rcur = 0.5*rcur;
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Serializer: allocation
+
+          -- ALGLIB --
+             Copyright 02.02.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rbfalloc(alglib.serializer s,
+            rbfmodel model)
+        {
+            
+            //
+            // Header
+            //
+            s.alloc_entry();
+            s.alloc_entry();
+            
+            //
+            // Data
+            //
+            s.alloc_entry();
+            s.alloc_entry();
+            s.alloc_entry();
+            s.alloc_entry();
+            nearestneighbor.kdtreealloc(s, model.tree);
+            apserv.allocrealmatrix(s, model.xc, -1, -1);
+            apserv.allocrealmatrix(s, model.wr, -1, -1);
+            s.alloc_entry();
+            apserv.allocrealmatrix(s, model.v, -1, -1);
+        }
+
+
+        /*************************************************************************
+        Serializer: serialization
+
+          -- ALGLIB --
+             Copyright 02.02.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rbfserialize(alglib.serializer s,
+            rbfmodel model)
+        {
+            
+            //
+            // Header
+            //
+            s.serialize_int(scodes.getrbfserializationcode());
+            s.serialize_int(rbffirstversion);
+            
+            //
+            // Data
+            //
+            s.serialize_int(model.nx);
+            s.serialize_int(model.ny);
+            s.serialize_int(model.nc);
+            s.serialize_int(model.nl);
+            nearestneighbor.kdtreeserialize(s, model.tree);
+            apserv.serializerealmatrix(s, model.xc, -1, -1);
+            apserv.serializerealmatrix(s, model.wr, -1, -1);
+            s.serialize_double(model.rmax);
+            apserv.serializerealmatrix(s, model.v, -1, -1);
+        }
+
+
+        /*************************************************************************
+        Serializer: unserialization
+
+          -- ALGLIB --
+             Copyright 02.02.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rbfunserialize(alglib.serializer s,
+            rbfmodel model)
+        {
+            int i0 = 0;
+            int i1 = 0;
+            int nx = 0;
+            int ny = 0;
+
+            
+            //
+            // Header
+            //
+            i0 = s.unserialize_int();
+            alglib.ap.assert(i0==scodes.getrbfserializationcode(), "RBFUnserialize: stream header corrupted");
+            i1 = s.unserialize_int();
+            alglib.ap.assert(i1==rbffirstversion, "RBFUnserialize: stream header corrupted");
+            
+            //
+            // Unserialize primary model parameters, initialize model.
+            //
+            // It is necessary to call RBFCreate() because some internal fields
+            // which are NOT unserialized will need initialization.
+            //
+            nx = s.unserialize_int();
+            ny = s.unserialize_int();
+            rbfcreate(nx, ny, model);
+            model.nc = s.unserialize_int();
+            model.nl = s.unserialize_int();
+            nearestneighbor.kdtreeunserialize(s, model.tree);
+            apserv.unserializerealmatrix(s, ref model.xc);
+            apserv.unserializerealmatrix(s, ref model.wr);
+            model.rmax = s.unserialize_double();
+            apserv.unserializerealmatrix(s, ref model.v);
+        }
+
+
+        /*************************************************************************
+        This function changes centers allocation algorithm to one which  allocates
+        centers on a regular grid with step equal to H  in  all  dimensions.  This
+        function won't have effect until next call to RBFBuildModel().
+
+        Central  point  of  the  grid  is  located  exactly  in  the middle of the 
+        bounding rectangle for our dataset. Grid countinues from the center to the
+        bounds of the bounding rectangle, and makes one step beyond  bounds  (i.e. 
+        leftmost/rightmost/topmost/... lines of the grid are located just  outside
+        of the bounding rectangle).
+
+        INPUT PARAMETERS:
+            S       -   RBF model, initialized by RBFCreate() call.
+            H       -   grid step, H>0
+
+        NOTE: this   function  has   some   serialization-related  subtleties.  We
+              recommend you to study serialization examples from ALGLIB  Reference
+              Manual if you want to perform serialization of your models.
+
+          -- ALGLIB --
+             Copyright 13.12.2011 by Bochkanov Sergey
+        *************************************************************************/
+        private static void rbfgridregular(rbfmodel s,
+            double h)
+        {
+            alglib.ap.assert(math.isfinite(h) && (double)(h)>(double)(0), "RBFGridRegular: H<0, INF or NAN");
+            s.h = h;
+            s.gridtype = 1;
+        }
+
+
+        /*************************************************************************
+        This function changes centers allocation algorithm to one which  allocates
+        centers exactly at the dataset points (one input point = one center). This
+        function won't have effect until next call to RBFBuildModel().
+
+        INPUT PARAMETERS:
+            S       -   RBF model, initialized by RBFCreate() call
+
+        NOTE: this   function  has   some   serialization-related  subtleties.  We
+              recommend you to study serialization examples from ALGLIB  Reference
+              Manual if you want to perform serialization of your models.
+
+          -- ALGLIB --
+             Copyright 13.12.2011 by Bochkanov Sergey
+        *************************************************************************/
+        private static void rbfgridpoints(rbfmodel s)
+        {
+            s.gridtype = 2;
+        }
+
+
+        /*************************************************************************
+        This function sets centers, defined by user.
+
+        This function overrides results of the previous calls, i.e. multiple calls
+        of this function will result in only the last set being added.
+
+        INPUT PARAMETERS:
+            S      -   RBF model, initialized by RBFCreate() call.
+          CXY      -   centers, array[N,NX]. Centers must be distinct, 
+                       non-distinct centers are not supported.
+           NC      -   number of centers
+
+        NOTE: this   function  has   some   serialization-related  subtleties.  We
+              recommend you to study serialization examples from ALGLIB  Reference
+              Manual if you want to perform serialization of your models.
+
+          -- ALGLIB --
+             Copyright 13.12.2011 by Bochkanov Sergey
+        *************************************************************************/
+        private static void rbfgridspecial(rbfmodel s,
+            double[,] cxy,
+            int nc)
+        {
+            int i = 0;
+            int j = 0;
+
+            alglib.ap.assert(nc>0, "RBFGridSpecial: N<0");
+            alglib.ap.assert(nc<=alglib.ap.rows(cxy), "RBFGridSpecial: Length(CXY)<NC");
+            s.gridtype = 3;
+            s.nc = nc;
+            s.xc = new double[s.nc, mxnx];
+            for(i=0; i<=s.nc-1; i++)
+            {
+                for(j=0; j<=mxnx-1; j++)
+                {
+                    s.xc[i,j] = 0;
+                }
+                for(j=0; j<=s.nx-1; j++)
+                {
+                    s.xc[i,j] = cxy[i,j];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This function changes radii calculation algorithm to one which  makes  all
+        radii equal to the same fixed value R. This  function  won't  have  effect
+        until next call to RBFBuildModel().
+
+        IMPORTANT: you should use this function with caution because too  large  R
+        will make model fitting algorithm unstable, while too small  R  will  make
+        perfect, but useless fit (it will be non-smooth, with sharp  peaks  around
+        dataset points).
+
+        INPUT PARAMETERS:
+            S       -   RBF model, initialized by RBFCreate() call
+            R       -   radius, R>0
+
+        NOTE: this   function  has   some   serialization-related  subtleties.  We
+              recommend you to study serialization examples from ALGLIB  Reference
+              Manual if you want to perform serialization of your models.
+
+          -- ALGLIB --
+             Copyright 13.12.2011 by Bochkanov Sergey
+        *************************************************************************/
+        private static void rbfradfixed(rbfmodel s,
+            double r)
+        {
+            alglib.ap.assert(math.isfinite(r) && (double)(r)>(double)(0), "RBFRadFixed: R<=0, infinite or NAN");
+            s.fixrad = true;
+            s.radvalue = r;
+        }
+
+
+        /*************************************************************************
+        This function changes radii  calculation  algorithm  to  one  which  makes
+        radius for I-th node equal to R[i]=DistNN[i]*Q, where:
+        * R[i] is a radius calculated by the algorithm
+        * DistNN[i] is distance from I-th center to its nearest neighbor center
+        * Q is a scale parameter, which should be within [0.75,1.50], with
+          recommended value equal to 1.0
+        * after performing radii calculation, radii are transformed  in  order  to
+          avoid situation when single outlier has very large radius and influences
+          many points across entire dataset. Transformation has following form:
+               new_r[i] = min(r[i],Z*median(r[]))
+           where r[i] is I-th radius, median()  is  a median  radius across entire
+           dataset, Z is user-specified value which controls amount  of  deviation
+           from median radius.
+
+        This function won't have effect until next call to RBFBuildModel().
+
+        The idea behind this algorithm is to choose radii corresponding  to  basis
+        functions is such way that I-th radius is approximately equal to  distance
+        from I-th center to its nearest neighbor. In this case  interactions  with
+        distant points will be insignificant, and we  will  get  well  conditioned
+        basis.
+
+        Properties of this basis depend on the value of Q:
+        * Q<0.75 will give perfectly conditioned basis,  but  terrible  smoothness
+          properties (RBF interpolant will have sharp peaks around function values)
+        * Q>1.5 will lead to badly conditioned systems and slow convergence of the
+          underlying linear solver (although smoothness will be very good)
+        * Q around 1.0 gives good balance between smoothness and condition number
+
+
+        INPUT PARAMETERS:
+            S       -   RBF model, initialized by RBFCreate() call
+            Q       -   radius coefficient, Q>0
+            Z       -   z-parameter, Z>0
+            
+        Default value of Q is equal to 1.0
+        Default value of Z is equal to 5.0
+
+        NOTE: this   function  has   some   serialization-related  subtleties.  We
+              recommend you to study serialization examples from ALGLIB  Reference
+              Manual if you want to perform serialization of your models.
+
+          -- ALGLIB --
+             Copyright 13.12.2011 by Bochkanov Sergey
+        *************************************************************************/
+        private static void rbfradnn(rbfmodel s,
+            double q,
+            double z)
+        {
+            alglib.ap.assert(math.isfinite(q) && (double)(q)>(double)(0), "RBFRadNN: Q<=0, infinite or NAN");
+            alglib.ap.assert(math.isfinite(z) && (double)(z)>(double)(0), "RBFRadNN: Z<=0, infinite or NAN");
+            s.fixrad = false;
+            s.radvalue = q;
+            s.radzvalue = z;
+        }
+
+
+        private static bool buildlinearmodel(double[,] x,
+            ref double[,] y,
+            int n,
+            int ny,
+            int modeltype,
+            ref double[,] v)
+        {
+            bool result = new bool();
+            double[] tmpy = new double[0];
+            double[,] a = new double[0,0];
+            double scaling = 0;
+            double[] shifting = new double[0];
+            double mn = 0;
+            double mx = 0;
+            double[] c = new double[0];
+            lsfit.lsfitreport rep = new lsfit.lsfitreport();
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int info = 0;
+
+            v = new double[0,0];
+
+            alglib.ap.assert(n>=0, "BuildLinearModel: N<0");
+            alglib.ap.assert(ny>0, "BuildLinearModel: NY<=0");
+            
+            //
+            // Handle degenerate case (N=0)
+            //
+            result = true;
+            v = new double[ny, mxnx+1];
+            if( n==0 )
+            {
+                for(j=0; j<=mxnx; j++)
+                {
+                    for(i=0; i<=ny-1; i++)
+                    {
+                        v[i,j] = 0;
+                    }
+                }
+                return result;
+            }
+            
+            //
+            // Allocate temporaries
+            //
+            tmpy = new double[n];
+            
+            //
+            // General linear model.
+            //
+            if( modeltype==1 )
+            {
+                
+                //
+                // Calculate scaling/shifting, transform variables, prepare LLS problem
+                //
+                a = new double[n, mxnx+1];
+                shifting = new double[mxnx];
+                scaling = 0;
+                for(i=0; i<=mxnx-1; i++)
+                {
+                    mn = x[0,i];
+                    mx = mn;
+                    for(j=1; j<=n-1; j++)
+                    {
+                        if( (double)(mn)>(double)(x[j,i]) )
+                        {
+                            mn = x[j,i];
+                        }
+                        if( (double)(mx)<(double)(x[j,i]) )
+                        {
+                            mx = x[j,i];
+                        }
+                    }
+                    scaling = Math.Max(scaling, mx-mn);
+                    shifting[i] = 0.5*(mx+mn);
+                }
+                if( (double)(scaling)==(double)(0) )
+                {
+                    scaling = 1;
+                }
+                else
+                {
+                    scaling = 0.5*scaling;
+                }
+                for(i=0; i<=n-1; i++)
+                {
+                    for(j=0; j<=mxnx-1; j++)
+                    {
+                        a[i,j] = (x[i,j]-shifting[j])/scaling;
+                    }
+                }
+                for(i=0; i<=n-1; i++)
+                {
+                    a[i,mxnx] = 1;
+                }
+                
+                //
+                // Solve linear system in transformed variables, make backward 
+                //
+                for(i=0; i<=ny-1; i++)
+                {
+                    for(j=0; j<=n-1; j++)
+                    {
+                        tmpy[j] = y[j,i];
+                    }
+                    lsfit.lsfitlinear(tmpy, a, n, mxnx+1, ref info, ref c, rep);
+                    if( info<=0 )
+                    {
+                        result = false;
+                        return result;
+                    }
+                    for(j=0; j<=mxnx-1; j++)
+                    {
+                        v[i,j] = c[j]/scaling;
+                    }
+                    v[i,mxnx] = c[mxnx];
+                    for(j=0; j<=mxnx-1; j++)
+                    {
+                        v[i,mxnx] = v[i,mxnx]-shifting[j]*v[i,j];
+                    }
+                    for(j=0; j<=n-1; j++)
+                    {
+                        for(k=0; k<=mxnx-1; k++)
+                        {
+                            y[j,i] = y[j,i]-x[j,k]*v[i,k];
+                        }
+                        y[j,i] = y[j,i]-v[i,mxnx];
+                    }
+                }
+                return result;
+            }
+            
+            //
+            // Constant model, very simple
+            //
+            if( modeltype==2 )
+            {
+                for(i=0; i<=ny-1; i++)
+                {
+                    for(j=0; j<=mxnx; j++)
+                    {
+                        v[i,j] = 0;
+                    }
+                    for(j=0; j<=n-1; j++)
+                    {
+                        v[i,mxnx] = v[i,mxnx]+y[j,i];
+                    }
+                    if( n>0 )
+                    {
+                        v[i,mxnx] = v[i,mxnx]/n;
+                    }
+                    for(j=0; j<=n-1; j++)
+                    {
+                        y[j,i] = y[j,i]-v[i,mxnx];
+                    }
+                }
+                return result;
+            }
+            
+            //
+            // Zero model
+            //
+            alglib.ap.assert(modeltype==3, "BuildLinearModel: unknown model type");
+            for(i=0; i<=ny-1; i++)
+            {
+                for(j=0; j<=mxnx; j++)
+                {
+                    v[i,j] = 0;
+                }
+            }
+            return result;
+        }
+
+
+        private static void buildrbfmodellsqr(double[,] x,
+            ref double[,] y,
+            double[,] xc,
+            double[] r,
+            int n,
+            int nc,
+            int ny,
+            nearestneighbor.kdtree pointstree,
+            nearestneighbor.kdtree centerstree,
+            double epsort,
+            double epserr,
+            int maxits,
+            ref int gnnz,
+            ref int snnz,
+            ref double[,] w,
+            ref int info,
+            ref int iterationscount,
+            ref int nmv)
+        {
+            linlsqr.linlsqrstate state = new linlsqr.linlsqrstate();
+            linlsqr.linlsqrreport lsqrrep = new linlsqr.linlsqrreport();
+            sparse.sparsematrix spg = new sparse.sparsematrix();
+            sparse.sparsematrix sps = new sparse.sparsematrix();
+            int[] nearcenterscnt = new int[0];
+            int[] nearpointscnt = new int[0];
+            int[] skipnearpointscnt = new int[0];
+            int[] farpointscnt = new int[0];
+            int maxnearcenterscnt = 0;
+            int maxnearpointscnt = 0;
+            int maxfarpointscnt = 0;
+            int sumnearcenterscnt = 0;
+            int sumnearpointscnt = 0;
+            int sumfarpointscnt = 0;
+            double maxrad = 0;
+            int[] pointstags = new int[0];
+            int[] centerstags = new int[0];
+            double[,] nearpoints = new double[0,0];
+            double[,] nearcenters = new double[0,0];
+            double[,] farpoints = new double[0,0];
+            int tmpi = 0;
+            int pointscnt = 0;
+            int centerscnt = 0;
+            double[] xcx = new double[0];
+            double[] tmpy = new double[0];
+            double[] tc = new double[0];
+            double[] g = new double[0];
+            double[] c = new double[0];
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int sind = 0;
+            double[,] a = new double[0,0];
+            double vv = 0;
+            double vx = 0;
+            double vy = 0;
+            double vz = 0;
+            double vr = 0;
+            double gnorm2 = 0;
+            double[] tmp0 = new double[0];
+            double[] tmp1 = new double[0];
+            double[] tmp2 = new double[0];
+            double fx = 0;
+            double[,] xx = new double[0,0];
+            double[,] cx = new double[0,0];
+            double mrad = 0;
+            int i_ = 0;
+
+            gnnz = 0;
+            snnz = 0;
+            w = new double[0,0];
+            info = 0;
+            iterationscount = 0;
+            nmv = 0;
+
+            
+            //
+            // Handle special cases: NC=0
+            //
+            if( nc==0 )
+            {
+                info = 1;
+                iterationscount = 0;
+                nmv = 0;
+                return;
+            }
+            
+            //
+            // Prepare for general case, NC>0
+            //
+            xcx = new double[mxnx];
+            pointstags = new int[n];
+            centerstags = new int[nc];
+            info = -1;
+            iterationscount = 0;
+            nmv = 0;
+            
+            //
+            // This block prepares quantities used to compute approximate cardinal basis functions (ACBFs):
+            // * NearCentersCnt[]   -   array[NC], whose elements store number of near centers used to build ACBF
+            // * NearPointsCnt[]    -   array[NC], number of near points used to build ACBF
+            // * FarPointsCnt[]     -   array[NC], number of far points (ones where ACBF is nonzero)
+            // * MaxNearCentersCnt  -   max(NearCentersCnt)
+            // * MaxNearPointsCnt   -   max(NearPointsCnt)
+            // * SumNearCentersCnt  -   sum(NearCentersCnt)
+            // * SumNearPointsCnt   -   sum(NearPointsCnt)
+            // * SumFarPointsCnt    -   sum(FarPointsCnt)
+            //
+            nearcenterscnt = new int[nc];
+            nearpointscnt = new int[nc];
+            skipnearpointscnt = new int[nc];
+            farpointscnt = new int[nc];
+            maxnearcenterscnt = 0;
+            maxnearpointscnt = 0;
+            maxfarpointscnt = 0;
+            sumnearcenterscnt = 0;
+            sumnearpointscnt = 0;
+            sumfarpointscnt = 0;
+            for(i=0; i<=nc-1; i++)
+            {
+                for(j=0; j<=mxnx-1; j++)
+                {
+                    xcx[j] = xc[i,j];
+                }
+                
+                //
+                // Determine number of near centers and maximum radius of near centers
+                //
+                nearcenterscnt[i] = nearestneighbor.kdtreequeryrnn(centerstree, xcx, r[i]*rbfnearradius, true);
+                nearestneighbor.kdtreequeryresultstags(centerstree, ref centerstags);
+                maxrad = 0;
+                for(j=0; j<=nearcenterscnt[i]-1; j++)
+                {
+                    maxrad = Math.Max(maxrad, Math.Abs(r[centerstags[j]]));
+                }
+                
+                //
+                // Determine number of near points (ones which used to build ACBF)
+                // and skipped points (the most near points which are NOT used to build ACBF
+                // and are NOT included in the near points count
+                //
+                skipnearpointscnt[i] = nearestneighbor.kdtreequeryrnn(pointstree, xcx, 0.1*r[i], true);
+                nearpointscnt[i] = nearestneighbor.kdtreequeryrnn(pointstree, xcx, (r[i]+maxrad)*rbfnearradius, true)-skipnearpointscnt[i];
+                alglib.ap.assert(nearpointscnt[i]>=0, "BuildRBFModelLSQR: internal error");
+                
+                //
+                // Determine number of far points
+                //
+                farpointscnt[i] = nearestneighbor.kdtreequeryrnn(pointstree, xcx, Math.Max(r[i]*rbfnearradius+maxrad*rbffarradius, r[i]*rbffarradius), true);
+                
+                //
+                // calculate sum and max, make some basic checks
+                //
+                alglib.ap.assert(nearcenterscnt[i]>0, "BuildRBFModelLSQR: internal error");
+                maxnearcenterscnt = Math.Max(maxnearcenterscnt, nearcenterscnt[i]);
+                maxnearpointscnt = Math.Max(maxnearpointscnt, nearpointscnt[i]);
+                maxfarpointscnt = Math.Max(maxfarpointscnt, farpointscnt[i]);
+                sumnearcenterscnt = sumnearcenterscnt+nearcenterscnt[i];
+                sumnearpointscnt = sumnearpointscnt+nearpointscnt[i];
+                sumfarpointscnt = sumfarpointscnt+farpointscnt[i];
+            }
+            snnz = sumnearcenterscnt;
+            gnnz = sumfarpointscnt;
+            alglib.ap.assert(maxnearcenterscnt>0, "BuildRBFModelLSQR: internal error");
+            
+            //
+            // Allocate temporaries.
+            //
+            // NOTE: we want to avoid allocation of zero-size arrays, so we
+            //       use max(desired_size,1) instead of desired_size when performing
+            //       memory allocation.
+            //
+            a = new double[maxnearpointscnt+maxnearcenterscnt, maxnearcenterscnt];
+            tmpy = new double[maxnearpointscnt+maxnearcenterscnt];
+            g = new double[maxnearcenterscnt];
+            c = new double[maxnearcenterscnt];
+            nearcenters = new double[maxnearcenterscnt, mxnx];
+            nearpoints = new double[Math.Max(maxnearpointscnt, 1), mxnx];
+            farpoints = new double[Math.Max(maxfarpointscnt, 1), mxnx];
+            
+            //
+            // fill matrix SpG
+            //
+            sparse.sparsecreate(n, nc, gnnz, spg);
+            sparse.sparsecreate(nc, nc, snnz, sps);
+            for(i=0; i<=nc-1; i++)
+            {
+                centerscnt = nearcenterscnt[i];
+                
+                //
+                // main center
+                //
+                for(j=0; j<=mxnx-1; j++)
+                {
+                    xcx[j] = xc[i,j];
+                }
+                
+                //
+                // center's tree
+                //
+                tmpi = nearestneighbor.kdtreequeryknn(centerstree, xcx, centerscnt, true);
+                alglib.ap.assert(tmpi==centerscnt, "BuildRBFModelLSQR: internal error");
+                nearestneighbor.kdtreequeryresultsx(centerstree, ref cx);
+                nearestneighbor.kdtreequeryresultstags(centerstree, ref centerstags);
+                
+                //
+                // point's tree
+                //
+                mrad = 0;
+                for(j=0; j<=centerscnt-1; j++)
+                {
+                    mrad = Math.Max(mrad, r[centerstags[j]]);
+                }
+                
+                //
+                // we need to be sure that 'CTree' contains
+                // at least one side center
+                //
+                sparse.sparseset(sps, i, i, 1);
+                c[0] = 1.0;
+                for(j=1; j<=centerscnt-1; j++)
+                {
+                    c[j] = 0.0;
+                }
+                if( centerscnt>1 && nearpointscnt[i]>0 )
+                {
+                    
+                    //
+                    // first KDTree request for points
+                    //
+                    pointscnt = nearpointscnt[i];
+                    tmpi = nearestneighbor.kdtreequeryknn(pointstree, xcx, skipnearpointscnt[i]+nearpointscnt[i], true);
+                    alglib.ap.assert(tmpi==skipnearpointscnt[i]+nearpointscnt[i], "BuildRBFModelLSQR: internal error");
+                    nearestneighbor.kdtreequeryresultsx(pointstree, ref xx);
+                    sind = skipnearpointscnt[i];
+                    for(j=0; j<=pointscnt-1; j++)
+                    {
+                        vx = xx[sind+j,0];
+                        vy = xx[sind+j,1];
+                        vz = xx[sind+j,2];
+                        for(k=0; k<=centerscnt-1; k++)
+                        {
+                            vr = 0.0;
+                            vv = vx-cx[k,0];
+                            vr = vr+vv*vv;
+                            vv = vy-cx[k,1];
+                            vr = vr+vv*vv;
+                            vv = vz-cx[k,2];
+                            vr = vr+vv*vv;
+                            vv = r[centerstags[k]];
+                            a[j,k] = Math.Exp(-(vr/(vv*vv)));
+                        }
+                    }
+                    for(j=0; j<=centerscnt-1; j++)
+                    {
+                        g[j] = Math.Exp(-((math.sqr(xcx[0]-cx[j,0])+math.sqr(xcx[1]-cx[j,1])+math.sqr(xcx[2]-cx[j,2]))/math.sqr(r[centerstags[j]])));
+                    }
+                    
+                    //
+                    // calculate the problem
+                    //
+                    gnorm2 = 0.0;
+                    for(i_=0; i_<=centerscnt-1;i_++)
+                    {
+                        gnorm2 += g[i_]*g[i_];
+                    }
+                    for(j=0; j<=pointscnt-1; j++)
+                    {
+                        vv = 0.0;
+                        for(i_=0; i_<=centerscnt-1;i_++)
+                        {
+                            vv += a[j,i_]*g[i_];
+                        }
+                        vv = vv/gnorm2;
+                        tmpy[j] = -vv;
+                        for(i_=0; i_<=centerscnt-1;i_++)
+                        {
+                            a[j,i_] = a[j,i_] - vv*g[i_];
+                        }
+                    }
+                    for(j=pointscnt; j<=pointscnt+centerscnt-1; j++)
+                    {
+                        for(k=0; k<=centerscnt-1; k++)
+                        {
+                            a[j,k] = 0.0;
+                        }
+                        a[j,j-pointscnt] = 1.0E-6;
+                        tmpy[j] = 0.0;
+                    }
+                    fbls.fblssolvels(ref a, ref tmpy, pointscnt+centerscnt, centerscnt, ref tmp0, ref tmp1, ref tmp2);
+                    for(i_=0; i_<=centerscnt-1;i_++)
+                    {
+                        c[i_] = tmpy[i_];
+                    }
+                    vv = 0.0;
+                    for(i_=0; i_<=centerscnt-1;i_++)
+                    {
+                        vv += g[i_]*c[i_];
+                    }
+                    vv = vv/gnorm2;
+                    for(i_=0; i_<=centerscnt-1;i_++)
+                    {
+                        c[i_] = c[i_] - vv*g[i_];
+                    }
+                    vv = 1/gnorm2;
+                    for(i_=0; i_<=centerscnt-1;i_++)
+                    {
+                        c[i_] = c[i_] + vv*g[i_];
+                    }
+                    for(j=0; j<=centerscnt-1; j++)
+                    {
+                        sparse.sparseset(sps, i, centerstags[j], c[j]);
+                    }
+                }
+                
+                //
+                // second KDTree request for points
+                //
+                pointscnt = farpointscnt[i];
+                tmpi = nearestneighbor.kdtreequeryknn(pointstree, xcx, pointscnt, true);
+                alglib.ap.assert(tmpi==pointscnt, "BuildRBFModelLSQR: internal error");
+                nearestneighbor.kdtreequeryresultsx(pointstree, ref xx);
+                nearestneighbor.kdtreequeryresultstags(pointstree, ref pointstags);
+                
+                //
+                //fill SpG matrix
+                //
+                for(j=0; j<=pointscnt-1; j++)
+                {
+                    fx = 0;
+                    vx = xx[j,0];
+                    vy = xx[j,1];
+                    vz = xx[j,2];
+                    for(k=0; k<=centerscnt-1; k++)
+                    {
+                        vr = 0.0;
+                        vv = vx-cx[k,0];
+                        vr = vr+vv*vv;
+                        vv = vy-cx[k,1];
+                        vr = vr+vv*vv;
+                        vv = vz-cx[k,2];
+                        vr = vr+vv*vv;
+                        vv = r[centerstags[k]];
+                        vv = vv*vv;
+                        fx = fx+c[k]*Math.Exp(-(vr/vv));
+                    }
+                    sparse.sparseset(spg, pointstags[j], i, fx);
+                }
+            }
+            sparse.sparseconverttocrs(spg);
+            sparse.sparseconverttocrs(sps);
+            
+            //
+            // solve by LSQR method
+            //
+            tmpy = new double[n];
+            tc = new double[nc];
+            w = new double[nc, ny];
+            linlsqr.linlsqrcreate(n, nc, state);
+            linlsqr.linlsqrsetcond(state, epsort, epserr, maxits);
+            for(i=0; i<=ny-1; i++)
+            {
+                for(j=0; j<=n-1; j++)
+                {
+                    tmpy[j] = y[j,i];
+                }
+                linlsqr.linlsqrsolvesparse(state, spg, tmpy);
+                linlsqr.linlsqrresults(state, ref c, lsqrrep);
+                if( lsqrrep.terminationtype<=0 )
+                {
+                    info = -4;
+                    return;
+                }
+                sparse.sparsemtv(sps, c, ref tc);
+                for(j=0; j<=nc-1; j++)
+                {
+                    w[j,i] = tc[j];
+                }
+                iterationscount = iterationscount+lsqrrep.iterationscount;
+                nmv = nmv+lsqrrep.nmv;
+            }
+            info = 1;
+        }
+
+
+        private static void buildrbfmlayersmodellsqr(double[,] x,
+            ref double[,] y,
+            ref double[,] xc,
+            double rval,
+            ref double[] r,
+            int n,
+            ref int nc,
+            int ny,
+            int nlayers,
+            nearestneighbor.kdtree centerstree,
+            double epsort,
+            double epserr,
+            int maxits,
+            double lambdav,
+            ref int annz,
+            ref double[,] w,
+            ref int info,
+            ref int iterationscount,
+            ref int nmv)
+        {
+            linlsqr.linlsqrstate state = new linlsqr.linlsqrstate();
+            linlsqr.linlsqrreport lsqrrep = new linlsqr.linlsqrreport();
+            sparse.sparsematrix spa = new sparse.sparsematrix();
+            double anorm = 0;
+            double[] omega = new double[0];
+            double[] xx = new double[0];
+            double[] tmpy = new double[0];
+            double[,] cx = new double[0,0];
+            double yval = 0;
+            int nec = 0;
+            int[] centerstags = new int[0];
+            int layer = 0;
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            double v = 0;
+            double rmaxbefore = 0;
+            double rmaxafter = 0;
+
+            xc = new double[0,0];
+            r = new double[0];
+            nc = 0;
+            annz = 0;
+            w = new double[0,0];
+            info = 0;
+            iterationscount = 0;
+            nmv = 0;
+
+            alglib.ap.assert(nlayers>=0, "BuildRBFMLayersModelLSQR: invalid argument(NLayers<0)");
+            alglib.ap.assert(n>=0, "BuildRBFMLayersModelLSQR: invalid argument(N<0)");
+            alglib.ap.assert(mxnx>0 && mxnx<=3, "BuildRBFMLayersModelLSQR: internal error(invalid global const MxNX: either MxNX<=0 or MxNX>3)");
+            annz = 0;
+            if( n==0 || nlayers==0 )
+            {
+                info = 1;
+                iterationscount = 0;
+                nmv = 0;
+                return;
+            }
+            nc = n*nlayers;
+            xx = new double[mxnx];
+            centerstags = new int[n];
+            xc = new double[nc, mxnx];
+            r = new double[nc];
+            for(i=0; i<=nc-1; i++)
+            {
+                for(j=0; j<=mxnx-1; j++)
+                {
+                    xc[i,j] = x[i%n,j];
+                }
+            }
+            for(i=0; i<=nc-1; i++)
+            {
+                r[i] = rval/Math.Pow(2, i/n);
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                centerstags[i] = i;
+            }
+            nearestneighbor.kdtreebuildtagged(xc, centerstags, n, mxnx, 0, 2, centerstree);
+            omega = new double[n];
+            tmpy = new double[n];
+            w = new double[nc, ny];
+            info = -1;
+            iterationscount = 0;
+            nmv = 0;
+            linlsqr.linlsqrcreate(n, n, state);
+            linlsqr.linlsqrsetcond(state, epsort, epserr, maxits);
+            linlsqr.linlsqrsetlambdai(state, 1.0E-6);
+            
+            //
+            // calculate number of non-zero elements for sparse matrix
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                for(j=0; j<=mxnx-1; j++)
+                {
+                    xx[j] = x[i,j];
+                }
+                annz = annz+nearestneighbor.kdtreequeryrnn(centerstree, xx, r[0]*rbfmlradius, true);
+            }
+            for(layer=0; layer<=nlayers-1; layer++)
+            {
+                
+                //
+                // Fill sparse matrix, calculate norm(A)
+                //
+                anorm = 0.0;
+                sparse.sparsecreate(n, n, annz, spa);
+                for(i=0; i<=n-1; i++)
+                {
+                    for(j=0; j<=mxnx-1; j++)
+                    {
+                        xx[j] = x[i,j];
+                    }
+                    nec = nearestneighbor.kdtreequeryrnn(centerstree, xx, r[layer*n]*rbfmlradius, true);
+                    nearestneighbor.kdtreequeryresultsx(centerstree, ref cx);
+                    nearestneighbor.kdtreequeryresultstags(centerstree, ref centerstags);
+                    for(j=0; j<=nec-1; j++)
+                    {
+                        v = Math.Exp(-((math.sqr(xx[0]-cx[j,0])+math.sqr(xx[1]-cx[j,1])+math.sqr(xx[2]-cx[j,2]))/math.sqr(r[layer*n+centerstags[j]])));
+                        sparse.sparseset(spa, i, centerstags[j], v);
+                        anorm = anorm+math.sqr(v);
+                    }
+                }
+                anorm = Math.Sqrt(anorm);
+                sparse.sparseconverttocrs(spa);
+                
+                //
+                // Calculate maximum residual before adding new layer.
+                // This value is not used by algorithm, the only purpose is to make debugging easier.
+                //
+                rmaxbefore = 0.0;
+                for(j=0; j<=n-1; j++)
+                {
+                    for(i=0; i<=ny-1; i++)
+                    {
+                        rmaxbefore = Math.Max(rmaxbefore, Math.Abs(y[j,i]));
+                    }
+                }
+                
+                //
+                // Process NY dimensions of the target function
+                //
+                for(i=0; i<=ny-1; i++)
+                {
+                    for(j=0; j<=n-1; j++)
+                    {
+                        tmpy[j] = y[j,i];
+                    }
+                    
+                    //
+                    // calculate Omega for current layer
+                    //
+                    linlsqr.linlsqrsetlambdai(state, lambdav*anorm/n);
+                    linlsqr.linlsqrsolvesparse(state, spa, tmpy);
+                    linlsqr.linlsqrresults(state, ref omega, lsqrrep);
+                    if( lsqrrep.terminationtype<=0 )
+                    {
+                        info = -4;
+                        return;
+                    }
+                    
+                    //
+                    // calculate error for current layer
+                    //
+                    for(j=0; j<=n-1; j++)
+                    {
+                        yval = 0;
+                        for(k=0; k<=mxnx-1; k++)
+                        {
+                            xx[k] = x[j,k];
+                        }
+                        nec = nearestneighbor.kdtreequeryrnn(centerstree, xx, r[layer*n]*rbffarradius, true);
+                        nearestneighbor.kdtreequeryresultsx(centerstree, ref cx);
+                        nearestneighbor.kdtreequeryresultstags(centerstree, ref centerstags);
+                        for(k=0; k<=nec-1; k++)
+                        {
+                            yval = yval+omega[centerstags[k]]*Math.Exp(-((math.sqr(xx[0]-cx[k,0])+math.sqr(xx[1]-cx[k,1])+math.sqr(xx[2]-cx[k,2]))/math.sqr(r[layer*n+centerstags[k]])));
+                        }
+                        y[j,i] = y[j,i]-yval;
+                    }
+                    
+                    //
+                    // write Omega in out parameter W
+                    //
+                    for(j=0; j<=n-1; j++)
+                    {
+                        w[layer*n+j,i] = omega[j];
+                    }
+                    iterationscount = iterationscount+lsqrrep.iterationscount;
+                    nmv = nmv+lsqrrep.nmv;
+                }
+                
+                //
+                // Calculate maximum residual before adding new layer.
+                // This value is not used by algorithm, the only purpose is to make debugging easier.
+                //
+                rmaxafter = 0.0;
+                for(j=0; j<=n-1; j++)
+                {
+                    for(i=0; i<=ny-1; i++)
+                    {
+                        rmaxafter = Math.Max(rmaxafter, Math.Abs(y[j,i]));
+                    }
+                }
+            }
+            info = 1;
+        }
+
+
+    }
+    public class spline2d
+    {
+        /*************************************************************************
+        2-dimensional spline inteprolant
+        *************************************************************************/
+        public class spline2dinterpolant
+        {
+            public int k;
+            public double[] c;
+            public spline2dinterpolant()
+            {
+                c = new double[0];
+            }
+        };
+
+
+
+
+        /*************************************************************************
+        This subroutine builds bilinear spline coefficients table.
+
+        Input parameters:
+            X   -   spline abscissas, array[0..N-1]
+            Y   -   spline ordinates, array[0..M-1]
+            F   -   function values, array[0..M-1,0..N-1]
+            M,N -   grid size, M>=2, N>=2
+
+        Output parameters:
+            C   -   spline interpolant
+
+          -- ALGLIB PROJECT --
+             Copyright 05.07.2007 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spline2dbuildbilinear(double[] x,
+            double[] y,
+            double[,] f,
+            int m,
+            int n,
+            spline2dinterpolant c)
+        {
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int tblsize = 0;
+            int shift = 0;
+            double t = 0;
+            double[,] dx = new double[0,0];
+            double[,] dy = new double[0,0];
+            double[,] dxy = new double[0,0];
+
+            x = (double[])x.Clone();
+            y = (double[])y.Clone();
+            f = (double[,])f.Clone();
+
+            alglib.ap.assert(n>=2 && m>=2, "Spline2DBuildBilinear: N<2 or M<2!");
+            
+            //
+            // Sort points
+            //
+            for(j=0; j<=n-1; j++)
+            {
+                k = j;
+                for(i=j+1; i<=n-1; i++)
+                {
+                    if( (double)(x[i])<(double)(x[k]) )
+                    {
+                        k = i;
+                    }
+                }
+                if( k!=j )
+                {
+                    for(i=0; i<=m-1; i++)
+                    {
+                        t = f[i,j];
+                        f[i,j] = f[i,k];
+                        f[i,k] = t;
+                    }
+                    t = x[j];
+                    x[j] = x[k];
+                    x[k] = t;
+                }
+            }
+            for(i=0; i<=m-1; i++)
+            {
+                k = i;
+                for(j=i+1; j<=m-1; j++)
+                {
+                    if( (double)(y[j])<(double)(y[k]) )
+                    {
+                        k = j;
+                    }
+                }
+                if( k!=i )
+                {
+                    for(j=0; j<=n-1; j++)
+                    {
+                        t = f[i,j];
+                        f[i,j] = f[k,j];
+                        f[k,j] = t;
+                    }
+                    t = y[i];
+                    y[i] = y[k];
+                    y[k] = t;
+                }
+            }
+            
+            //
+            // Fill C:
+            //  C[0]            -   length(C)
+            //  C[1]            -   type(C):
+            //                      -1 = bilinear interpolant
+            //                      -3 = general cubic spline
+            //                           (see BuildBicubicSpline)
+            //  C[2]:
+            //      N (x count)
+            //  C[3]:
+            //      M (y count)
+            //  C[4]...C[4+N-1]:
+            //      x[i], i = 0...N-1
+            //  C[4+N]...C[4+N+M-1]:
+            //      y[i], i = 0...M-1
+            //  C[4+N+M]...C[4+N+M+(N*M-1)]:
+            //      f(i,j) table. f(0,0), f(0, 1), f(0,2) and so on...
+            //
+            c.k = 1;
+            tblsize = 4+n+m+n*m;
+            c.c = new double[tblsize-1+1];
+            c.c[0] = tblsize;
+            c.c[1] = -1;
+            c.c[2] = n;
+            c.c[3] = m;
+            for(i=0; i<=n-1; i++)
+            {
+                c.c[4+i] = x[i];
+            }
+            for(i=0; i<=m-1; i++)
+            {
+                c.c[4+n+i] = y[i];
+            }
+            for(i=0; i<=m-1; i++)
+            {
+                for(j=0; j<=n-1; j++)
+                {
+                    shift = i*n+j;
+                    c.c[4+n+m+shift] = f[i,j];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This subroutine builds bicubic spline coefficients table.
+
+        Input parameters:
+            X   -   spline abscissas, array[0..N-1]
+            Y   -   spline ordinates, array[0..M-1]
+            F   -   function values, array[0..M-1,0..N-1]
+            M,N -   grid size, M>=2, N>=2
+
+        Output parameters:
+            C   -   spline interpolant
+
+          -- ALGLIB PROJECT --
+             Copyright 05.07.2007 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spline2dbuildbicubic(double[] x,
+            double[] y,
+            double[,] f,
+            int m,
+            int n,
+            spline2dinterpolant c)
+        {
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int tblsize = 0;
+            int shift = 0;
+            double t = 0;
+            double[,] dx = new double[0,0];
+            double[,] dy = new double[0,0];
+            double[,] dxy = new double[0,0];
+
+            x = (double[])x.Clone();
+            y = (double[])y.Clone();
+            f = (double[,])f.Clone();
+
+            alglib.ap.assert(n>=2 && m>=2, "BuildBicubicSpline: N<2 or M<2!");
+            
+            //
+            // Sort points
+            //
+            for(j=0; j<=n-1; j++)
+            {
+                k = j;
+                for(i=j+1; i<=n-1; i++)
+                {
+                    if( (double)(x[i])<(double)(x[k]) )
+                    {
+                        k = i;
+                    }
+                }
+                if( k!=j )
+                {
+                    for(i=0; i<=m-1; i++)
+                    {
+                        t = f[i,j];
+                        f[i,j] = f[i,k];
+                        f[i,k] = t;
+                    }
+                    t = x[j];
+                    x[j] = x[k];
+                    x[k] = t;
+                }
+            }
+            for(i=0; i<=m-1; i++)
+            {
+                k = i;
+                for(j=i+1; j<=m-1; j++)
+                {
+                    if( (double)(y[j])<(double)(y[k]) )
+                    {
+                        k = j;
+                    }
+                }
+                if( k!=i )
+                {
+                    for(j=0; j<=n-1; j++)
+                    {
+                        t = f[i,j];
+                        f[i,j] = f[k,j];
+                        f[k,j] = t;
+                    }
+                    t = y[i];
+                    y[i] = y[k];
+                    y[k] = t;
+                }
+            }
+            
+            //
+            // Fill C:
+            //  C[0]            -   length(C)
+            //  C[1]            -   type(C):
+            //                      -1 = bilinear interpolant
+            //                           (see BuildBilinearInterpolant)
+            //                      -3 = general cubic spline
+            //  C[2]:
+            //      N (x count)
+            //  C[3]:
+            //      M (y count)
+            //  C[4]...C[4+N-1]:
+            //      x[i], i = 0...N-1
+            //  C[4+N]...C[4+N+M-1]:
+            //      y[i], i = 0...M-1
+            //  C[4+N+M]...C[4+N+M+(N*M-1)]:
+            //      f(i,j) table. f(0,0), f(0, 1), f(0,2) and so on...
+            //  C[4+N+M+N*M]...C[4+N+M+(2*N*M-1)]:
+            //      df(i,j)/dx table.
+            //  C[4+N+M+2*N*M]...C[4+N+M+(3*N*M-1)]:
+            //      df(i,j)/dy table.
+            //  C[4+N+M+3*N*M]...C[4+N+M+(4*N*M-1)]:
+            //      d2f(i,j)/dxdy table.
+            //
+            c.k = 3;
+            tblsize = 4+n+m+4*n*m;
+            c.c = new double[tblsize-1+1];
+            c.c[0] = tblsize;
+            c.c[1] = -3;
+            c.c[2] = n;
+            c.c[3] = m;
+            for(i=0; i<=n-1; i++)
+            {
+                c.c[4+i] = x[i];
+            }
+            for(i=0; i<=m-1; i++)
+            {
+                c.c[4+n+i] = y[i];
+            }
+            bicubiccalcderivatives(f, x, y, m, n, ref dx, ref dy, ref dxy);
+            for(i=0; i<=m-1; i++)
+            {
+                for(j=0; j<=n-1; j++)
+                {
+                    shift = i*n+j;
+                    c.c[4+n+m+shift] = f[i,j];
+                    c.c[4+n+m+n*m+shift] = dx[i,j];
+                    c.c[4+n+m+2*n*m+shift] = dy[i,j];
+                    c.c[4+n+m+3*n*m+shift] = dxy[i,j];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This subroutine calculates the value of the bilinear or bicubic spline  at
+        the given point X.
+
+        Input parameters:
+            C   -   coefficients table.
+                    Built by BuildBilinearSpline or BuildBicubicSpline.
+            X, Y-   point
+
+        Result:
+            S(x,y)
+
+          -- ALGLIB PROJECT --
+             Copyright 05.07.2007 by Bochkanov Sergey
+        *************************************************************************/
+        public static double spline2dcalc(spline2dinterpolant c,
+            double x,
+            double y)
+        {
+            double result = 0;
+            double v = 0;
+            double vx = 0;
+            double vy = 0;
+            double vxy = 0;
+
+            spline2ddiff(c, x, y, ref v, ref vx, ref vy, ref vxy);
+            result = v;
+            return result;
+        }
+
+
+        /*************************************************************************
+        This subroutine calculates the value of the bilinear or bicubic spline  at
+        the given point X and its derivatives.
+
+        Input parameters:
+            C   -   spline interpolant.
+            X, Y-   point
+
+        Output parameters:
+            F   -   S(x,y)
+            FX  -   dS(x,y)/dX
+            FY  -   dS(x,y)/dY
+            FXY -   d2S(x,y)/dXdY
+
+          -- ALGLIB PROJECT --
+             Copyright 05.07.2007 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spline2ddiff(spline2dinterpolant c,
+            double x,
+            double y,
+            ref double f,
+            ref double fx,
+            ref double fy,
+            ref double fxy)
+        {
+            int n = 0;
+            int m = 0;
+            double t = 0;
+            double dt = 0;
+            double u = 0;
+            double du = 0;
+            int ix = 0;
+            int iy = 0;
+            int l = 0;
+            int r = 0;
+            int h = 0;
+            int shift1 = 0;
+            int s1 = 0;
+            int s2 = 0;
+            int s3 = 0;
+            int s4 = 0;
+            int sf = 0;
+            int sfx = 0;
+            int sfy = 0;
+            int sfxy = 0;
+            double y1 = 0;
+            double y2 = 0;
+            double y3 = 0;
+            double y4 = 0;
+            double v = 0;
+            double t0 = 0;
+            double t1 = 0;
+            double t2 = 0;
+            double t3 = 0;
+            double u0 = 0;
+            double u1 = 0;
+            double u2 = 0;
+            double u3 = 0;
+
+            f = 0;
+            fx = 0;
+            fy = 0;
+            fxy = 0;
+
+            alglib.ap.assert((int)Math.Round(c.c[1])==-1 || (int)Math.Round(c.c[1])==-3, "Spline2DDiff: incorrect C!");
+            n = (int)Math.Round(c.c[2]);
+            m = (int)Math.Round(c.c[3]);
+            
+            //
+            // Binary search in the [ x[0], ..., x[n-2] ] (x[n-1] is not included)
+            //
+            l = 4;
+            r = 4+n-2+1;
+            while( l!=r-1 )
+            {
+                h = (l+r)/2;
+                if( (double)(c.c[h])>=(double)(x) )
+                {
+                    r = h;
+                }
+                else
+                {
+                    l = h;
+                }
+            }
+            t = (x-c.c[l])/(c.c[l+1]-c.c[l]);
+            dt = 1.0/(c.c[l+1]-c.c[l]);
+            ix = l-4;
+            
+            //
+            // Binary search in the [ y[0], ..., y[m-2] ] (y[m-1] is not included)
+            //
+            l = 4+n;
+            r = 4+n+(m-2)+1;
+            while( l!=r-1 )
+            {
+                h = (l+r)/2;
+                if( (double)(c.c[h])>=(double)(y) )
+                {
+                    r = h;
+                }
+                else
+                {
+                    l = h;
+                }
+            }
+            u = (y-c.c[l])/(c.c[l+1]-c.c[l]);
+            du = 1.0/(c.c[l+1]-c.c[l]);
+            iy = l-(4+n);
+            
+            //
+            // Prepare F, dF/dX, dF/dY, d2F/dXdY
+            //
+            f = 0;
+            fx = 0;
+            fy = 0;
+            fxy = 0;
+            
+            //
+            // Bilinear interpolation
+            //
+            if( (int)Math.Round(c.c[1])==-1 )
+            {
+                shift1 = 4+n+m;
+                y1 = c.c[shift1+n*iy+ix];
+                y2 = c.c[shift1+n*iy+(ix+1)];
+                y3 = c.c[shift1+n*(iy+1)+(ix+1)];
+                y4 = c.c[shift1+n*(iy+1)+ix];
+                f = (1-t)*(1-u)*y1+t*(1-u)*y2+t*u*y3+(1-t)*u*y4;
+                fx = (-((1-u)*y1)+(1-u)*y2+u*y3-u*y4)*dt;
+                fy = (-((1-t)*y1)-t*y2+t*y3+(1-t)*y4)*du;
+                fxy = (y1-y2+y3-y4)*du*dt;
+                return;
+            }
+            
+            //
+            // Bicubic interpolation
+            //
+            if( (int)Math.Round(c.c[1])==-3 )
+            {
+                
+                //
+                // Prepare info
+                //
+                t0 = 1;
+                t1 = t;
+                t2 = math.sqr(t);
+                t3 = t*t2;
+                u0 = 1;
+                u1 = u;
+                u2 = math.sqr(u);
+                u3 = u*u2;
+                sf = 4+n+m;
+                sfx = 4+n+m+n*m;
+                sfy = 4+n+m+2*n*m;
+                sfxy = 4+n+m+3*n*m;
+                s1 = n*iy+ix;
+                s2 = n*iy+(ix+1);
+                s3 = n*(iy+1)+(ix+1);
+                s4 = n*(iy+1)+ix;
+                
+                //
+                // Calculate
+                //
+                v = 1*c.c[sf+s1];
+                f = f+v*t0*u0;
+                v = 1*c.c[sfy+s1]/du;
+                f = f+v*t0*u1;
+                fy = fy+1*v*t0*u0*du;
+                v = -(3*c.c[sf+s1])+3*c.c[sf+s4]-2*c.c[sfy+s1]/du-1*c.c[sfy+s4]/du;
+                f = f+v*t0*u2;
+                fy = fy+2*v*t0*u1*du;
+                v = 2*c.c[sf+s1]-2*c.c[sf+s4]+1*c.c[sfy+s1]/du+1*c.c[sfy+s4]/du;
+                f = f+v*t0*u3;
+                fy = fy+3*v*t0*u2*du;
+                v = 1*c.c[sfx+s1]/dt;
+                f = f+v*t1*u0;
+                fx = fx+1*v*t0*u0*dt;
+                v = 1*c.c[sfxy+s1]/(dt*du);
+                f = f+v*t1*u1;
+                fx = fx+1*v*t0*u1*dt;
+                fy = fy+1*v*t1*u0*du;
+                fxy = fxy+1*v*t0*u0*dt*du;
+                v = -(3*c.c[sfx+s1]/dt)+3*c.c[sfx+s4]/dt-2*c.c[sfxy+s1]/(dt*du)-1*c.c[sfxy+s4]/(dt*du);
+                f = f+v*t1*u2;
+                fx = fx+1*v*t0*u2*dt;
+                fy = fy+2*v*t1*u1*du;
+                fxy = fxy+2*v*t0*u1*dt*du;
+                v = 2*c.c[sfx+s1]/dt-2*c.c[sfx+s4]/dt+1*c.c[sfxy+s1]/(dt*du)+1*c.c[sfxy+s4]/(dt*du);
+                f = f+v*t1*u3;
+                fx = fx+1*v*t0*u3*dt;
+                fy = fy+3*v*t1*u2*du;
+                fxy = fxy+3*v*t0*u2*dt*du;
+                v = -(3*c.c[sf+s1])+3*c.c[sf+s2]-2*c.c[sfx+s1]/dt-1*c.c[sfx+s2]/dt;
+                f = f+v*t2*u0;
+                fx = fx+2*v*t1*u0*dt;
+                v = -(3*c.c[sfy+s1]/du)+3*c.c[sfy+s2]/du-2*c.c[sfxy+s1]/(dt*du)-1*c.c[sfxy+s2]/(dt*du);
+                f = f+v*t2*u1;
+                fx = fx+2*v*t1*u1*dt;
+                fy = fy+1*v*t2*u0*du;
+                fxy = fxy+2*v*t1*u0*dt*du;
+                v = 9*c.c[sf+s1]-9*c.c[sf+s2]+9*c.c[sf+s3]-9*c.c[sf+s4]+6*c.c[sfx+s1]/dt+3*c.c[sfx+s2]/dt-3*c.c[sfx+s3]/dt-6*c.c[sfx+s4]/dt+6*c.c[sfy+s1]/du-6*c.c[sfy+s2]/du-3*c.c[sfy+s3]/du+3*c.c[sfy+s4]/du+4*c.c[sfxy+s1]/(dt*du)+2*c.c[sfxy+s2]/(dt*du)+1*c.c[sfxy+s3]/(dt*du)+2*c.c[sfxy+s4]/(dt*du);
+                f = f+v*t2*u2;
+                fx = fx+2*v*t1*u2*dt;
+                fy = fy+2*v*t2*u1*du;
+                fxy = fxy+4*v*t1*u1*dt*du;
+                v = -(6*c.c[sf+s1])+6*c.c[sf+s2]-6*c.c[sf+s3]+6*c.c[sf+s4]-4*c.c[sfx+s1]/dt-2*c.c[sfx+s2]/dt+2*c.c[sfx+s3]/dt+4*c.c[sfx+s4]/dt-3*c.c[sfy+s1]/du+3*c.c[sfy+s2]/du+3*c.c[sfy+s3]/du-3*c.c[sfy+s4]/du-2*c.c[sfxy+s1]/(dt*du)-1*c.c[sfxy+s2]/(dt*du)-1*c.c[sfxy+s3]/(dt*du)-2*c.c[sfxy+s4]/(dt*du);
+                f = f+v*t2*u3;
+                fx = fx+2*v*t1*u3*dt;
+                fy = fy+3*v*t2*u2*du;
+                fxy = fxy+6*v*t1*u2*dt*du;
+                v = 2*c.c[sf+s1]-2*c.c[sf+s2]+1*c.c[sfx+s1]/dt+1*c.c[sfx+s2]/dt;
+                f = f+v*t3*u0;
+                fx = fx+3*v*t2*u0*dt;
+                v = 2*c.c[sfy+s1]/du-2*c.c[sfy+s2]/du+1*c.c[sfxy+s1]/(dt*du)+1*c.c[sfxy+s2]/(dt*du);
+                f = f+v*t3*u1;
+                fx = fx+3*v*t2*u1*dt;
+                fy = fy+1*v*t3*u0*du;
+                fxy = fxy+3*v*t2*u0*dt*du;
+                v = -(6*c.c[sf+s1])+6*c.c[sf+s2]-6*c.c[sf+s3]+6*c.c[sf+s4]-3*c.c[sfx+s1]/dt-3*c.c[sfx+s2]/dt+3*c.c[sfx+s3]/dt+3*c.c[sfx+s4]/dt-4*c.c[sfy+s1]/du+4*c.c[sfy+s2]/du+2*c.c[sfy+s3]/du-2*c.c[sfy+s4]/du-2*c.c[sfxy+s1]/(dt*du)-2*c.c[sfxy+s2]/(dt*du)-1*c.c[sfxy+s3]/(dt*du)-1*c.c[sfxy+s4]/(dt*du);
+                f = f+v*t3*u2;
+                fx = fx+3*v*t2*u2*dt;
+                fy = fy+2*v*t3*u1*du;
+                fxy = fxy+6*v*t2*u1*dt*du;
+                v = 4*c.c[sf+s1]-4*c.c[sf+s2]+4*c.c[sf+s3]-4*c.c[sf+s4]+2*c.c[sfx+s1]/dt+2*c.c[sfx+s2]/dt-2*c.c[sfx+s3]/dt-2*c.c[sfx+s4]/dt+2*c.c[sfy+s1]/du-2*c.c[sfy+s2]/du-2*c.c[sfy+s3]/du+2*c.c[sfy+s4]/du+1*c.c[sfxy+s1]/(dt*du)+1*c.c[sfxy+s2]/(dt*du)+1*c.c[sfxy+s3]/(dt*du)+1*c.c[sfxy+s4]/(dt*du);
+                f = f+v*t3*u3;
+                fx = fx+3*v*t2*u3*dt;
+                fy = fy+3*v*t3*u2*du;
+                fxy = fxy+9*v*t2*u2*dt*du;
+                return;
+            }
+        }
+
+
+        /*************************************************************************
+        This subroutine unpacks two-dimensional spline into the coefficients table
+
+        Input parameters:
+            C   -   spline interpolant.
+
+        Result:
+            M, N-   grid size (x-axis and y-axis)
+            Tbl -   coefficients table, unpacked format,
+                    [0..(N-1)*(M-1)-1, 0..19].
+                    For I = 0...M-2, J=0..N-2:
+                        K =  I*(N-1)+J
+                        Tbl[K,0] = X[j]
+                        Tbl[K,1] = X[j+1]
+                        Tbl[K,2] = Y[i]
+                        Tbl[K,3] = Y[i+1]
+                        Tbl[K,4] = C00
+                        Tbl[K,5] = C01
+                        Tbl[K,6] = C02
+                        Tbl[K,7] = C03
+                        Tbl[K,8] = C10
+                        Tbl[K,9] = C11
+                        ...
+                        Tbl[K,19] = C33
+                    On each grid square spline is equals to:
+                        S(x) = SUM(c[i,j]*(x^i)*(y^j), i=0..3, j=0..3)
+                        t = x-x[j]
+                        u = y-y[i]
+
+          -- ALGLIB PROJECT --
+             Copyright 29.06.2007 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spline2dunpack(spline2dinterpolant c,
+            ref int m,
+            ref int n,
+            ref double[,] tbl)
+        {
+            int i = 0;
+            int j = 0;
+            int ci = 0;
+            int cj = 0;
+            int k = 0;
+            int p = 0;
+            int shift = 0;
+            int s1 = 0;
+            int s2 = 0;
+            int s3 = 0;
+            int s4 = 0;
+            int sf = 0;
+            int sfx = 0;
+            int sfy = 0;
+            int sfxy = 0;
+            double y1 = 0;
+            double y2 = 0;
+            double y3 = 0;
+            double y4 = 0;
+            double dt = 0;
+            double du = 0;
+
+            m = 0;
+            n = 0;
+            tbl = new double[0,0];
+
+            alglib.ap.assert((int)Math.Round(c.c[1])==-3 || (int)Math.Round(c.c[1])==-1, "SplineUnpack2D: incorrect C!");
+            n = (int)Math.Round(c.c[2]);
+            m = (int)Math.Round(c.c[3]);
+            tbl = new double[(n-1)*(m-1)-1+1, 19+1];
+            
+            //
+            // Fill
+            //
+            for(i=0; i<=m-2; i++)
+            {
+                for(j=0; j<=n-2; j++)
+                {
+                    p = i*(n-1)+j;
+                    tbl[p,0] = c.c[4+j];
+                    tbl[p,1] = c.c[4+j+1];
+                    tbl[p,2] = c.c[4+n+i];
+                    tbl[p,3] = c.c[4+n+i+1];
+                    dt = 1/(tbl[p,1]-tbl[p,0]);
+                    du = 1/(tbl[p,3]-tbl[p,2]);
+                    
+                    //
+                    // Bilinear interpolation
+                    //
+                    if( (int)Math.Round(c.c[1])==-1 )
+                    {
+                        for(k=4; k<=19; k++)
+                        {
+                            tbl[p,k] = 0;
+                        }
+                        shift = 4+n+m;
+                        y1 = c.c[shift+n*i+j];
+                        y2 = c.c[shift+n*i+(j+1)];
+                        y3 = c.c[shift+n*(i+1)+(j+1)];
+                        y4 = c.c[shift+n*(i+1)+j];
+                        tbl[p,4] = y1;
+                        tbl[p,4+1*4+0] = y2-y1;
+                        tbl[p,4+0*4+1] = y4-y1;
+                        tbl[p,4+1*4+1] = y3-y2-y4+y1;
+                    }
+                    
+                    //
+                    // Bicubic interpolation
+                    //
+                    if( (int)Math.Round(c.c[1])==-3 )
+                    {
+                        sf = 4+n+m;
+                        sfx = 4+n+m+n*m;
+                        sfy = 4+n+m+2*n*m;
+                        sfxy = 4+n+m+3*n*m;
+                        s1 = n*i+j;
+                        s2 = n*i+(j+1);
+                        s3 = n*(i+1)+(j+1);
+                        s4 = n*(i+1)+j;
+                        tbl[p,4+0*4+0] = 1*c.c[sf+s1];
+                        tbl[p,4+0*4+1] = 1*c.c[sfy+s1]/du;
+                        tbl[p,4+0*4+2] = -(3*c.c[sf+s1])+3*c.c[sf+s4]-2*c.c[sfy+s1]/du-1*c.c[sfy+s4]/du;
+                        tbl[p,4+0*4+3] = 2*c.c[sf+s1]-2*c.c[sf+s4]+1*c.c[sfy+s1]/du+1*c.c[sfy+s4]/du;
+                        tbl[p,4+1*4+0] = 1*c.c[sfx+s1]/dt;
+                        tbl[p,4+1*4+1] = 1*c.c[sfxy+s1]/(dt*du);
+                        tbl[p,4+1*4+2] = -(3*c.c[sfx+s1]/dt)+3*c.c[sfx+s4]/dt-2*c.c[sfxy+s1]/(dt*du)-1*c.c[sfxy+s4]/(dt*du);
+                        tbl[p,4+1*4+3] = 2*c.c[sfx+s1]/dt-2*c.c[sfx+s4]/dt+1*c.c[sfxy+s1]/(dt*du)+1*c.c[sfxy+s4]/(dt*du);
+                        tbl[p,4+2*4+0] = -(3*c.c[sf+s1])+3*c.c[sf+s2]-2*c.c[sfx+s1]/dt-1*c.c[sfx+s2]/dt;
+                        tbl[p,4+2*4+1] = -(3*c.c[sfy+s1]/du)+3*c.c[sfy+s2]/du-2*c.c[sfxy+s1]/(dt*du)-1*c.c[sfxy+s2]/(dt*du);
+                        tbl[p,4+2*4+2] = 9*c.c[sf+s1]-9*c.c[sf+s2]+9*c.c[sf+s3]-9*c.c[sf+s4]+6*c.c[sfx+s1]/dt+3*c.c[sfx+s2]/dt-3*c.c[sfx+s3]/dt-6*c.c[sfx+s4]/dt+6*c.c[sfy+s1]/du-6*c.c[sfy+s2]/du-3*c.c[sfy+s3]/du+3*c.c[sfy+s4]/du+4*c.c[sfxy+s1]/(dt*du)+2*c.c[sfxy+s2]/(dt*du)+1*c.c[sfxy+s3]/(dt*du)+2*c.c[sfxy+s4]/(dt*du);
+                        tbl[p,4+2*4+3] = -(6*c.c[sf+s1])+6*c.c[sf+s2]-6*c.c[sf+s3]+6*c.c[sf+s4]-4*c.c[sfx+s1]/dt-2*c.c[sfx+s2]/dt+2*c.c[sfx+s3]/dt+4*c.c[sfx+s4]/dt-3*c.c[sfy+s1]/du+3*c.c[sfy+s2]/du+3*c.c[sfy+s3]/du-3*c.c[sfy+s4]/du-2*c.c[sfxy+s1]/(dt*du)-1*c.c[sfxy+s2]/(dt*du)-1*c.c[sfxy+s3]/(dt*du)-2*c.c[sfxy+s4]/(dt*du);
+                        tbl[p,4+3*4+0] = 2*c.c[sf+s1]-2*c.c[sf+s2]+1*c.c[sfx+s1]/dt+1*c.c[sfx+s2]/dt;
+                        tbl[p,4+3*4+1] = 2*c.c[sfy+s1]/du-2*c.c[sfy+s2]/du+1*c.c[sfxy+s1]/(dt*du)+1*c.c[sfxy+s2]/(dt*du);
+                        tbl[p,4+3*4+2] = -(6*c.c[sf+s1])+6*c.c[sf+s2]-6*c.c[sf+s3]+6*c.c[sf+s4]-3*c.c[sfx+s1]/dt-3*c.c[sfx+s2]/dt+3*c.c[sfx+s3]/dt+3*c.c[sfx+s4]/dt-4*c.c[sfy+s1]/du+4*c.c[sfy+s2]/du+2*c.c[sfy+s3]/du-2*c.c[sfy+s4]/du-2*c.c[sfxy+s1]/(dt*du)-2*c.c[sfxy+s2]/(dt*du)-1*c.c[sfxy+s3]/(dt*du)-1*c.c[sfxy+s4]/(dt*du);
+                        tbl[p,4+3*4+3] = 4*c.c[sf+s1]-4*c.c[sf+s2]+4*c.c[sf+s3]-4*c.c[sf+s4]+2*c.c[sfx+s1]/dt+2*c.c[sfx+s2]/dt-2*c.c[sfx+s3]/dt-2*c.c[sfx+s4]/dt+2*c.c[sfy+s1]/du-2*c.c[sfy+s2]/du-2*c.c[sfy+s3]/du+2*c.c[sfy+s4]/du+1*c.c[sfxy+s1]/(dt*du)+1*c.c[sfxy+s2]/(dt*du)+1*c.c[sfxy+s3]/(dt*du)+1*c.c[sfxy+s4]/(dt*du);
+                    }
+                    
+                    //
+                    // Rescale Cij
+                    //
+                    for(ci=0; ci<=3; ci++)
+                    {
+                        for(cj=0; cj<=3; cj++)
+                        {
+                            tbl[p,4+ci*4+cj] = tbl[p,4+ci*4+cj]*Math.Pow(dt, ci)*Math.Pow(du, cj);
+                        }
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This subroutine performs linear transformation of the spline argument.
+
+        Input parameters:
+            C       -   spline interpolant
+            AX, BX  -   transformation coefficients: x = A*t + B
+            AY, BY  -   transformation coefficients: y = A*u + B
+        Result:
+            C   -   transformed spline
+
+          -- ALGLIB PROJECT --
+             Copyright 30.06.2007 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spline2dlintransxy(spline2dinterpolant c,
+            double ax,
+            double bx,
+            double ay,
+            double by)
+        {
+            int i = 0;
+            int j = 0;
+            int n = 0;
+            int m = 0;
+            double v = 0;
+            double[] x = new double[0];
+            double[] y = new double[0];
+            double[,] f = new double[0,0];
+            int typec = 0;
+
+            typec = (int)Math.Round(c.c[1]);
+            alglib.ap.assert(typec==-3 || typec==-1, "Spline2DLinTransXY: incorrect C!");
+            n = (int)Math.Round(c.c[2]);
+            m = (int)Math.Round(c.c[3]);
+            x = new double[n-1+1];
+            y = new double[m-1+1];
+            f = new double[m-1+1, n-1+1];
+            for(j=0; j<=n-1; j++)
+            {
+                x[j] = c.c[4+j];
+            }
+            for(i=0; i<=m-1; i++)
+            {
+                y[i] = c.c[4+n+i];
+            }
+            for(i=0; i<=m-1; i++)
+            {
+                for(j=0; j<=n-1; j++)
+                {
+                    f[i,j] = c.c[4+n+m+i*n+j];
+                }
+            }
+            
+            //
+            // Special case: AX=0 or AY=0
+            //
+            if( (double)(ax)==(double)(0) )
+            {
+                for(i=0; i<=m-1; i++)
+                {
+                    v = spline2dcalc(c, bx, y[i]);
+                    for(j=0; j<=n-1; j++)
+                    {
+                        f[i,j] = v;
+                    }
+                }
+                if( typec==-3 )
+                {
+                    spline2dbuildbicubic(x, y, f, m, n, c);
+                }
+                if( typec==-1 )
+                {
+                    spline2dbuildbilinear(x, y, f, m, n, c);
+                }
+                ax = 1;
+                bx = 0;
+            }
+            if( (double)(ay)==(double)(0) )
+            {
+                for(j=0; j<=n-1; j++)
+                {
+                    v = spline2dcalc(c, x[j], by);
+                    for(i=0; i<=m-1; i++)
+                    {
+                        f[i,j] = v;
+                    }
+                }
+                if( typec==-3 )
+                {
+                    spline2dbuildbicubic(x, y, f, m, n, c);
+                }
+                if( typec==-1 )
+                {
+                    spline2dbuildbilinear(x, y, f, m, n, c);
+                }
+                ay = 1;
+                by = 0;
+            }
+            
+            //
+            // General case: AX<>0, AY<>0
+            // Unpack, scale and pack again.
+            //
+            for(j=0; j<=n-1; j++)
+            {
+                x[j] = (x[j]-bx)/ax;
+            }
+            for(i=0; i<=m-1; i++)
+            {
+                y[i] = (y[i]-by)/ay;
+            }
+            if( typec==-3 )
+            {
+                spline2dbuildbicubic(x, y, f, m, n, c);
+            }
+            if( typec==-1 )
+            {
+                spline2dbuildbilinear(x, y, f, m, n, c);
+            }
+        }
+
+
+        /*************************************************************************
+        This subroutine performs linear transformation of the spline.
+
+        Input parameters:
+            C   -   spline interpolant.
+            A, B-   transformation coefficients: S2(x,y) = A*S(x,y) + B
+            
+        Output parameters:
+            C   -   transformed spline
+
+          -- ALGLIB PROJECT --
+             Copyright 30.06.2007 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spline2dlintransf(spline2dinterpolant c,
+            double a,
+            double b)
+        {
+            int i = 0;
+            int j = 0;
+            int n = 0;
+            int m = 0;
+            double[] x = new double[0];
+            double[] y = new double[0];
+            double[,] f = new double[0,0];
+            int typec = 0;
+
+            typec = (int)Math.Round(c.c[1]);
+            alglib.ap.assert(typec==-3 || typec==-1, "Spline2DLinTransXY: incorrect C!");
+            n = (int)Math.Round(c.c[2]);
+            m = (int)Math.Round(c.c[3]);
+            x = new double[n-1+1];
+            y = new double[m-1+1];
+            f = new double[m-1+1, n-1+1];
+            for(j=0; j<=n-1; j++)
+            {
+                x[j] = c.c[4+j];
+            }
+            for(i=0; i<=m-1; i++)
+            {
+                y[i] = c.c[4+n+i];
+            }
+            for(i=0; i<=m-1; i++)
+            {
+                for(j=0; j<=n-1; j++)
+                {
+                    f[i,j] = a*c.c[4+n+m+i*n+j]+b;
+                }
+            }
+            if( typec==-3 )
+            {
+                spline2dbuildbicubic(x, y, f, m, n, c);
+            }
+            if( typec==-1 )
+            {
+                spline2dbuildbilinear(x, y, f, m, n, c);
+            }
+        }
+
+
+        /*************************************************************************
+        This subroutine makes the copy of the spline model.
+
+        Input parameters:
+            C   -   spline interpolant
+
+        Output parameters:
+            CC  -   spline copy
+
+          -- ALGLIB PROJECT --
+             Copyright 29.06.2007 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spline2dcopy(spline2dinterpolant c,
+            spline2dinterpolant cc)
+        {
+            int n = 0;
+            int i_ = 0;
+
+            alglib.ap.assert(c.k==1 || c.k==3, "Spline2DCopy: incorrect C!");
+            cc.k = c.k;
+            n = (int)Math.Round(c.c[0]);
+            cc.c = new double[n];
+            for(i_=0; i_<=n-1;i_++)
+            {
+                cc.c[i_] = c.c[i_];
+            }
+        }
+
+
+        /*************************************************************************
+        Bicubic spline resampling
+
+        Input parameters:
+            A           -   function values at the old grid,
+                            array[0..OldHeight-1, 0..OldWidth-1]
+            OldHeight   -   old grid height, OldHeight>1
+            OldWidth    -   old grid width, OldWidth>1
+            NewHeight   -   new grid height, NewHeight>1
+            NewWidth    -   new grid width, NewWidth>1
+            
+        Output parameters:
+            B           -   function values at the new grid,
+                            array[0..NewHeight-1, 0..NewWidth-1]
+
+          -- ALGLIB routine --
+             15 May, 2007
+             Copyright by Bochkanov Sergey
+        *************************************************************************/
+        public static void spline2dresamplebicubic(double[,] a,
+            int oldheight,
+            int oldwidth,
+            ref double[,] b,
+            int newheight,
+            int newwidth)
+        {
+            double[,] buf = new double[0,0];
+            double[] x = new double[0];
+            double[] y = new double[0];
+            spline1d.spline1dinterpolant c = new spline1d.spline1dinterpolant();
+            int i = 0;
+            int j = 0;
+            int mw = 0;
+            int mh = 0;
+
+            b = new double[0,0];
+
+            alglib.ap.assert(oldwidth>1 && oldheight>1, "Spline2DResampleBicubic: width/height less than 1");
+            alglib.ap.assert(newwidth>1 && newheight>1, "Spline2DResampleBicubic: width/height less than 1");
+            
+            //
+            // Prepare
+            //
+            mw = Math.Max(oldwidth, newwidth);
+            mh = Math.Max(oldheight, newheight);
+            b = new double[newheight-1+1, newwidth-1+1];
+            buf = new double[oldheight-1+1, newwidth-1+1];
+            x = new double[Math.Max(mw, mh)-1+1];
+            y = new double[Math.Max(mw, mh)-1+1];
+            
+            //
+            // Horizontal interpolation
+            //
+            for(i=0; i<=oldheight-1; i++)
+            {
+                
+                //
+                // Fill X, Y
+                //
+                for(j=0; j<=oldwidth-1; j++)
+                {
+                    x[j] = (double)j/(double)(oldwidth-1);
+                    y[j] = a[i,j];
+                }
+                
+                //
+                // Interpolate and place result into temporary matrix
+                //
+                spline1d.spline1dbuildcubic(x, y, oldwidth, 0, 0.0, 0, 0.0, c);
+                for(j=0; j<=newwidth-1; j++)
+                {
+                    buf[i,j] = spline1d.spline1dcalc(c, (double)j/(double)(newwidth-1));
+                }
+            }
+            
+            //
+            // Vertical interpolation
+            //
+            for(j=0; j<=newwidth-1; j++)
+            {
+                
+                //
+                // Fill X, Y
+                //
+                for(i=0; i<=oldheight-1; i++)
+                {
+                    x[i] = (double)i/(double)(oldheight-1);
+                    y[i] = buf[i,j];
+                }
+                
+                //
+                // Interpolate and place result into B
+                //
+                spline1d.spline1dbuildcubic(x, y, oldheight, 0, 0.0, 0, 0.0, c);
+                for(i=0; i<=newheight-1; i++)
+                {
+                    b[i,j] = spline1d.spline1dcalc(c, (double)i/(double)(newheight-1));
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Bilinear spline resampling
+
+        Input parameters:
+            A           -   function values at the old grid,
+                            array[0..OldHeight-1, 0..OldWidth-1]
+            OldHeight   -   old grid height, OldHeight>1
+            OldWidth    -   old grid width, OldWidth>1
+            NewHeight   -   new grid height, NewHeight>1
+            NewWidth    -   new grid width, NewWidth>1
+
+        Output parameters:
+            B           -   function values at the new grid,
+                            array[0..NewHeight-1, 0..NewWidth-1]
+
+          -- ALGLIB routine --
+             09.07.2007
+             Copyright by Bochkanov Sergey
+        *************************************************************************/
+        public static void spline2dresamplebilinear(double[,] a,
+            int oldheight,
+            int oldwidth,
+            ref double[,] b,
+            int newheight,
+            int newwidth)
+        {
+            int i = 0;
+            int j = 0;
+            int l = 0;
+            int c = 0;
+            double t = 0;
+            double u = 0;
+
+            b = new double[0,0];
+
+            b = new double[newheight-1+1, newwidth-1+1];
+            for(i=0; i<=newheight-1; i++)
+            {
+                for(j=0; j<=newwidth-1; j++)
+                {
+                    l = i*(oldheight-1)/(newheight-1);
+                    if( l==oldheight-1 )
+                    {
+                        l = oldheight-2;
+                    }
+                    u = (double)i/(double)(newheight-1)*(oldheight-1)-l;
+                    c = j*(oldwidth-1)/(newwidth-1);
+                    if( c==oldwidth-1 )
+                    {
+                        c = oldwidth-2;
+                    }
+                    t = (double)(j*(oldwidth-1))/(double)(newwidth-1)-c;
+                    b[i,j] = (1-t)*(1-u)*a[l,c]+t*(1-u)*a[l,c+1]+t*u*a[l+1,c+1]+(1-t)*u*a[l+1,c];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Internal subroutine.
+        Calculation of the first derivatives and the cross-derivative.
+        *************************************************************************/
+        private static void bicubiccalcderivatives(double[,] a,
+            double[] x,
+            double[] y,
+            int m,
+            int n,
+            ref double[,] dx,
+            ref double[,] dy,
+            ref double[,] dxy)
+        {
+            int i = 0;
+            int j = 0;
+            double[] xt = new double[0];
+            double[] ft = new double[0];
+            double s = 0;
+            double ds = 0;
+            double d2s = 0;
+            spline1d.spline1dinterpolant c = new spline1d.spline1dinterpolant();
+
+            dx = new double[0,0];
+            dy = new double[0,0];
+            dxy = new double[0,0];
+
+            dx = new double[m, n];
+            dy = new double[m, n];
+            dxy = new double[m, n];
+            
+            //
+            // dF/dX
+            //
+            xt = new double[n];
+            ft = new double[n];
+            for(i=0; i<=m-1; i++)
+            {
+                for(j=0; j<=n-1; j++)
+                {
+                    xt[j] = x[j];
+                    ft[j] = a[i,j];
+                }
+                spline1d.spline1dbuildcubic(xt, ft, n, 0, 0.0, 0, 0.0, c);
+                for(j=0; j<=n-1; j++)
+                {
+                    spline1d.spline1ddiff(c, x[j], ref s, ref ds, ref d2s);
+                    dx[i,j] = ds;
+                }
+            }
+            
+            //
+            // dF/dY
+            //
+            xt = new double[m];
+            ft = new double[m];
+            for(j=0; j<=n-1; j++)
+            {
+                for(i=0; i<=m-1; i++)
+                {
+                    xt[i] = y[i];
+                    ft[i] = a[i,j];
+                }
+                spline1d.spline1dbuildcubic(xt, ft, m, 0, 0.0, 0, 0.0, c);
+                for(i=0; i<=m-1; i++)
+                {
+                    spline1d.spline1ddiff(c, y[i], ref s, ref ds, ref d2s);
+                    dy[i,j] = ds;
+                }
+            }
+            
+            //
+            // d2F/dXdY
+            //
+            xt = new double[n];
+            ft = new double[n];
+            for(i=0; i<=m-1; i++)
+            {
+                for(j=0; j<=n-1; j++)
+                {
+                    xt[j] = x[j];
+                    ft[j] = dy[i,j];
+                }
+                spline1d.spline1dbuildcubic(xt, ft, n, 0, 0.0, 0, 0.0, c);
+                for(j=0; j<=n-1; j++)
+                {
+                    spline1d.spline1ddiff(c, x[j], ref s, ref ds, ref d2s);
+                    dxy[i,j] = ds;
+                }
+            }
+        }
+
+
+    }
+}
+
Index: /trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.5.0/ALGLIB-3.5.0/linalg.cs
===================================================================
--- /trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.5.0/ALGLIB-3.5.0/linalg.cs	(revision 7673)
+++ /trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.5.0/ALGLIB-3.5.0/linalg.cs	(revision 7673)
@@ -0,0 +1,31834 @@
+/*************************************************************************
+Copyright (c) Sergey Bochkanov (ALGLIB project).
+
+>>> SOURCE LICENSE >>>
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation (www.fsf.org); either version 2 of the
+License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+A copy of the GNU General Public License is available at
+http://www.fsf.org/licensing/licenses
+>>> END OF LICENSE >>>
+*************************************************************************/
+#pragma warning disable 162
+#pragma warning disable 219
+using System;
+
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    Cache-oblivous complex "copy-and-transpose"
+
+    Input parameters:
+        M   -   number of rows
+        N   -   number of columns
+        A   -   source matrix, MxN submatrix is copied and transposed
+        IA  -   submatrix offset (row index)
+        JA  -   submatrix offset (column index)
+        B   -   destination matrix, must be large enough to store result
+        IB  -   submatrix offset (row index)
+        JB  -   submatrix offset (column index)
+    *************************************************************************/
+    public static void cmatrixtranspose(int m, int n, complex[,] a, int ia, int ja, ref complex[,] b, int ib, int jb)
+    {
+
+        ablas.cmatrixtranspose(m, n, a, ia, ja, ref b, ib, jb);
+        return;
+    }
+
+    /*************************************************************************
+    Cache-oblivous real "copy-and-transpose"
+
+    Input parameters:
+        M   -   number of rows
+        N   -   number of columns
+        A   -   source matrix, MxN submatrix is copied and transposed
+        IA  -   submatrix offset (row index)
+        JA  -   submatrix offset (column index)
+        B   -   destination matrix, must be large enough to store result
+        IB  -   submatrix offset (row index)
+        JB  -   submatrix offset (column index)
+    *************************************************************************/
+    public static void rmatrixtranspose(int m, int n, double[,] a, int ia, int ja, ref double[,] b, int ib, int jb)
+    {
+
+        ablas.rmatrixtranspose(m, n, a, ia, ja, ref b, ib, jb);
+        return;
+    }
+
+    /*************************************************************************
+    Copy
+
+    Input parameters:
+        M   -   number of rows
+        N   -   number of columns
+        A   -   source matrix, MxN submatrix is copied and transposed
+        IA  -   submatrix offset (row index)
+        JA  -   submatrix offset (column index)
+        B   -   destination matrix, must be large enough to store result
+        IB  -   submatrix offset (row index)
+        JB  -   submatrix offset (column index)
+    *************************************************************************/
+    public static void cmatrixcopy(int m, int n, complex[,] a, int ia, int ja, ref complex[,] b, int ib, int jb)
+    {
+
+        ablas.cmatrixcopy(m, n, a, ia, ja, ref b, ib, jb);
+        return;
+    }
+
+    /*************************************************************************
+    Copy
+
+    Input parameters:
+        M   -   number of rows
+        N   -   number of columns
+        A   -   source matrix, MxN submatrix is copied and transposed
+        IA  -   submatrix offset (row index)
+        JA  -   submatrix offset (column index)
+        B   -   destination matrix, must be large enough to store result
+        IB  -   submatrix offset (row index)
+        JB  -   submatrix offset (column index)
+    *************************************************************************/
+    public static void rmatrixcopy(int m, int n, double[,] a, int ia, int ja, ref double[,] b, int ib, int jb)
+    {
+
+        ablas.rmatrixcopy(m, n, a, ia, ja, ref b, ib, jb);
+        return;
+    }
+
+    /*************************************************************************
+    Rank-1 correction: A := A + u*v'
+
+    INPUT PARAMETERS:
+        M   -   number of rows
+        N   -   number of columns
+        A   -   target matrix, MxN submatrix is updated
+        IA  -   submatrix offset (row index)
+        JA  -   submatrix offset (column index)
+        U   -   vector #1
+        IU  -   subvector offset
+        V   -   vector #2
+        IV  -   subvector offset
+    *************************************************************************/
+    public static void cmatrixrank1(int m, int n, ref complex[,] a, int ia, int ja, ref complex[] u, int iu, ref complex[] v, int iv)
+    {
+
+        ablas.cmatrixrank1(m, n, ref a, ia, ja, ref u, iu, ref v, iv);
+        return;
+    }
+
+    /*************************************************************************
+    Rank-1 correction: A := A + u*v'
+
+    INPUT PARAMETERS:
+        M   -   number of rows
+        N   -   number of columns
+        A   -   target matrix, MxN submatrix is updated
+        IA  -   submatrix offset (row index)
+        JA  -   submatrix offset (column index)
+        U   -   vector #1
+        IU  -   subvector offset
+        V   -   vector #2
+        IV  -   subvector offset
+    *************************************************************************/
+    public static void rmatrixrank1(int m, int n, ref double[,] a, int ia, int ja, ref double[] u, int iu, ref double[] v, int iv)
+    {
+
+        ablas.rmatrixrank1(m, n, ref a, ia, ja, ref u, iu, ref v, iv);
+        return;
+    }
+
+    /*************************************************************************
+    Matrix-vector product: y := op(A)*x
+
+    INPUT PARAMETERS:
+        M   -   number of rows of op(A)
+                M>=0
+        N   -   number of columns of op(A)
+                N>=0
+        A   -   target matrix
+        IA  -   submatrix offset (row index)
+        JA  -   submatrix offset (column index)
+        OpA -   operation type:
+                * OpA=0     =>  op(A) = A
+                * OpA=1     =>  op(A) = A^T
+                * OpA=2     =>  op(A) = A^H
+        X   -   input vector
+        IX  -   subvector offset
+        IY  -   subvector offset
+        Y   -   preallocated matrix, must be large enough to store result
+
+    OUTPUT PARAMETERS:
+        Y   -   vector which stores result
+
+    if M=0, then subroutine does nothing.
+    if N=0, Y is filled by zeros.
+
+
+      -- ALGLIB routine --
+
+         28.01.2010
+         Bochkanov Sergey
+    *************************************************************************/
+    public static void cmatrixmv(int m, int n, complex[,] a, int ia, int ja, int opa, complex[] x, int ix, ref complex[] y, int iy)
+    {
+
+        ablas.cmatrixmv(m, n, a, ia, ja, opa, x, ix, ref y, iy);
+        return;
+    }
+
+    /*************************************************************************
+    Matrix-vector product: y := op(A)*x
+
+    INPUT PARAMETERS:
+        M   -   number of rows of op(A)
+        N   -   number of columns of op(A)
+        A   -   target matrix
+        IA  -   submatrix offset (row index)
+        JA  -   submatrix offset (column index)
+        OpA -   operation type:
+                * OpA=0     =>  op(A) = A
+                * OpA=1     =>  op(A) = A^T
+        X   -   input vector
+        IX  -   subvector offset
+        IY  -   subvector offset
+        Y   -   preallocated matrix, must be large enough to store result
+
+    OUTPUT PARAMETERS:
+        Y   -   vector which stores result
+
+    if M=0, then subroutine does nothing.
+    if N=0, Y is filled by zeros.
+
+
+      -- ALGLIB routine --
+
+         28.01.2010
+         Bochkanov Sergey
+    *************************************************************************/
+    public static void rmatrixmv(int m, int n, double[,] a, int ia, int ja, int opa, double[] x, int ix, ref double[] y, int iy)
+    {
+
+        ablas.rmatrixmv(m, n, a, ia, ja, opa, x, ix, ref y, iy);
+        return;
+    }
+
+    /*************************************************************************
+    This subroutine calculates X*op(A^-1) where:
+    * X is MxN general matrix
+    * A is NxN upper/lower triangular/unitriangular matrix
+    * "op" may be identity transformation, transposition, conjugate transposition
+
+    Multiplication result replaces X.
+    Cache-oblivious algorithm is used.
+
+    INPUT PARAMETERS
+        N   -   matrix size, N>=0
+        M   -   matrix size, N>=0
+        A       -   matrix, actial matrix is stored in A[I1:I1+N-1,J1:J1+N-1]
+        I1      -   submatrix offset
+        J1      -   submatrix offset
+        IsUpper -   whether matrix is upper triangular
+        IsUnit  -   whether matrix is unitriangular
+        OpType  -   transformation type:
+                    * 0 - no transformation
+                    * 1 - transposition
+                    * 2 - conjugate transposition
+        X   -   matrix, actial matrix is stored in X[I2:I2+M-1,J2:J2+N-1]
+        I2  -   submatrix offset
+        J2  -   submatrix offset
+
+      -- ALGLIB routine --
+         15.12.2009
+         Bochkanov Sergey
+    *************************************************************************/
+    public static void cmatrixrighttrsm(int m, int n, complex[,] a, int i1, int j1, bool isupper, bool isunit, int optype, ref complex[,] x, int i2, int j2)
+    {
+
+        ablas.cmatrixrighttrsm(m, n, a, i1, j1, isupper, isunit, optype, ref x, i2, j2);
+        return;
+    }
+
+    /*************************************************************************
+    This subroutine calculates op(A^-1)*X where:
+    * X is MxN general matrix
+    * A is MxM upper/lower triangular/unitriangular matrix
+    * "op" may be identity transformation, transposition, conjugate transposition
+
+    Multiplication result replaces X.
+    Cache-oblivious algorithm is used.
+
+    INPUT PARAMETERS
+        N   -   matrix size, N>=0
+        M   -   matrix size, N>=0
+        A       -   matrix, actial matrix is stored in A[I1:I1+M-1,J1:J1+M-1]
+        I1      -   submatrix offset
+        J1      -   submatrix offset
+        IsUpper -   whether matrix is upper triangular
+        IsUnit  -   whether matrix is unitriangular
+        OpType  -   transformation type:
+                    * 0 - no transformation
+                    * 1 - transposition
+                    * 2 - conjugate transposition
+        X   -   matrix, actial matrix is stored in X[I2:I2+M-1,J2:J2+N-1]
+        I2  -   submatrix offset
+        J2  -   submatrix offset
+
+      -- ALGLIB routine --
+         15.12.2009
+         Bochkanov Sergey
+    *************************************************************************/
+    public static void cmatrixlefttrsm(int m, int n, complex[,] a, int i1, int j1, bool isupper, bool isunit, int optype, ref complex[,] x, int i2, int j2)
+    {
+
+        ablas.cmatrixlefttrsm(m, n, a, i1, j1, isupper, isunit, optype, ref x, i2, j2);
+        return;
+    }
+
+    /*************************************************************************
+    This subroutine calculates X*op(A^-1) where:
+    * X is MxN general matrix
+    * A is NxN upper/lower triangular/unitriangular matrix
+    * "op" may be identity transformation, transposition
+
+    Multiplication result replaces X.
+    Cache-oblivious algorithm is used.
+
+    INPUT PARAMETERS
+        N   -   matrix size, N>=0
+        M   -   matrix size, N>=0
+        A       -   matrix, actial matrix is stored in A[I1:I1+N-1,J1:J1+N-1]
+        I1      -   submatrix offset
+        J1      -   submatrix offset
+        IsUpper -   whether matrix is upper triangular
+        IsUnit  -   whether matrix is unitriangular
+        OpType  -   transformation type:
+                    * 0 - no transformation
+                    * 1 - transposition
+        X   -   matrix, actial matrix is stored in X[I2:I2+M-1,J2:J2+N-1]
+        I2  -   submatrix offset
+        J2  -   submatrix offset
+
+      -- ALGLIB routine --
+         15.12.2009
+         Bochkanov Sergey
+    *************************************************************************/
+    public static void rmatrixrighttrsm(int m, int n, double[,] a, int i1, int j1, bool isupper, bool isunit, int optype, ref double[,] x, int i2, int j2)
+    {
+
+        ablas.rmatrixrighttrsm(m, n, a, i1, j1, isupper, isunit, optype, ref x, i2, j2);
+        return;
+    }
+
+    /*************************************************************************
+    This subroutine calculates op(A^-1)*X where:
+    * X is MxN general matrix
+    * A is MxM upper/lower triangular/unitriangular matrix
+    * "op" may be identity transformation, transposition
+
+    Multiplication result replaces X.
+    Cache-oblivious algorithm is used.
+
+    INPUT PARAMETERS
+        N   -   matrix size, N>=0
+        M   -   matrix size, N>=0
+        A       -   matrix, actial matrix is stored in A[I1:I1+M-1,J1:J1+M-1]
+        I1      -   submatrix offset
+        J1      -   submatrix offset
+        IsUpper -   whether matrix is upper triangular
+        IsUnit  -   whether matrix is unitriangular
+        OpType  -   transformation type:
+                    * 0 - no transformation
+                    * 1 - transposition
+        X   -   matrix, actial matrix is stored in X[I2:I2+M-1,J2:J2+N-1]
+        I2  -   submatrix offset
+        J2  -   submatrix offset
+
+      -- ALGLIB routine --
+         15.12.2009
+         Bochkanov Sergey
+    *************************************************************************/
+    public static void rmatrixlefttrsm(int m, int n, double[,] a, int i1, int j1, bool isupper, bool isunit, int optype, ref double[,] x, int i2, int j2)
+    {
+
+        ablas.rmatrixlefttrsm(m, n, a, i1, j1, isupper, isunit, optype, ref x, i2, j2);
+        return;
+    }
+
+    /*************************************************************************
+    This subroutine calculates  C=alpha*A*A^H+beta*C  or  C=alpha*A^H*A+beta*C
+    where:
+    * C is NxN Hermitian matrix given by its upper/lower triangle
+    * A is NxK matrix when A*A^H is calculated, KxN matrix otherwise
+
+    Additional info:
+    * cache-oblivious algorithm is used.
+    * multiplication result replaces C. If Beta=0, C elements are not used in
+      calculations (not multiplied by zero - just not referenced)
+    * if Alpha=0, A is not used (not multiplied by zero - just not referenced)
+    * if both Beta and Alpha are zero, C is filled by zeros.
+
+    INPUT PARAMETERS
+        N       -   matrix size, N>=0
+        K       -   matrix size, K>=0
+        Alpha   -   coefficient
+        A       -   matrix
+        IA      -   submatrix offset
+        JA      -   submatrix offset
+        OpTypeA -   multiplication type:
+                    * 0 - A*A^H is calculated
+                    * 2 - A^H*A is calculated
+        Beta    -   coefficient
+        C       -   matrix
+        IC      -   submatrix offset
+        JC      -   submatrix offset
+        IsUpper -   whether C is upper triangular or lower triangular
+
+      -- ALGLIB routine --
+         16.12.2009
+         Bochkanov Sergey
+    *************************************************************************/
+    public static void cmatrixsyrk(int n, int k, double alpha, complex[,] a, int ia, int ja, int optypea, double beta, ref complex[,] c, int ic, int jc, bool isupper)
+    {
+
+        ablas.cmatrixsyrk(n, k, alpha, a, ia, ja, optypea, beta, ref c, ic, jc, isupper);
+        return;
+    }
+
+    /*************************************************************************
+    This subroutine calculates  C=alpha*A*A^T+beta*C  or  C=alpha*A^T*A+beta*C
+    where:
+    * C is NxN symmetric matrix given by its upper/lower triangle
+    * A is NxK matrix when A*A^T is calculated, KxN matrix otherwise
+
+    Additional info:
+    * cache-oblivious algorithm is used.
+    * multiplication result replaces C. If Beta=0, C elements are not used in
+      calculations (not multiplied by zero - just not referenced)
+    * if Alpha=0, A is not used (not multiplied by zero - just not referenced)
+    * if both Beta and Alpha are zero, C is filled by zeros.
+
+    INPUT PARAMETERS
+        N       -   matrix size, N>=0
+        K       -   matrix size, K>=0
+        Alpha   -   coefficient
+        A       -   matrix
+        IA      -   submatrix offset
+        JA      -   submatrix offset
+        OpTypeA -   multiplication type:
+                    * 0 - A*A^T is calculated
+                    * 2 - A^T*A is calculated
+        Beta    -   coefficient
+        C       -   matrix
+        IC      -   submatrix offset
+        JC      -   submatrix offset
+        IsUpper -   whether C is upper triangular or lower triangular
+
+      -- ALGLIB routine --
+         16.12.2009
+         Bochkanov Sergey
+    *************************************************************************/
+    public static void rmatrixsyrk(int n, int k, double alpha, double[,] a, int ia, int ja, int optypea, double beta, ref double[,] c, int ic, int jc, bool isupper)
+    {
+
+        ablas.rmatrixsyrk(n, k, alpha, a, ia, ja, optypea, beta, ref c, ic, jc, isupper);
+        return;
+    }
+
+    /*************************************************************************
+    This subroutine calculates C = alpha*op1(A)*op2(B) +beta*C where:
+    * C is MxN general matrix
+    * op1(A) is MxK matrix
+    * op2(B) is KxN matrix
+    * "op" may be identity transformation, transposition, conjugate transposition
+
+    Additional info:
+    * cache-oblivious algorithm is used.
+    * multiplication result replaces C. If Beta=0, C elements are not used in
+      calculations (not multiplied by zero - just not referenced)
+    * if Alpha=0, A is not used (not multiplied by zero - just not referenced)
+    * if both Beta and Alpha are zero, C is filled by zeros.
+
+    INPUT PARAMETERS
+        M       -   matrix size, M>0
+        N       -   matrix size, N>0
+        K       -   matrix size, K>0
+        Alpha   -   coefficient
+        A       -   matrix
+        IA      -   submatrix offset
+        JA      -   submatrix offset
+        OpTypeA -   transformation type:
+                    * 0 - no transformation
+                    * 1 - transposition
+                    * 2 - conjugate transposition
+        B       -   matrix
+        IB      -   submatrix offset
+        JB      -   submatrix offset
+        OpTypeB -   transformation type:
+                    * 0 - no transformation
+                    * 1 - transposition
+                    * 2 - conjugate transposition
+        Beta    -   coefficient
+        C       -   matrix
+        IC      -   submatrix offset
+        JC      -   submatrix offset
+
+      -- ALGLIB routine --
+         16.12.2009
+         Bochkanov Sergey
+    *************************************************************************/
+    public static void cmatrixgemm(int m, int n, int k, complex alpha, complex[,] a, int ia, int ja, int optypea, complex[,] b, int ib, int jb, int optypeb, complex beta, ref complex[,] c, int ic, int jc)
+    {
+
+        ablas.cmatrixgemm(m, n, k, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, ref c, ic, jc);
+        return;
+    }
+
+    /*************************************************************************
+    This subroutine calculates C = alpha*op1(A)*op2(B) +beta*C where:
+    * C is MxN general matrix
+    * op1(A) is MxK matrix
+    * op2(B) is KxN matrix
+    * "op" may be identity transformation, transposition
+
+    Additional info:
+    * cache-oblivious algorithm is used.
+    * multiplication result replaces C. If Beta=0, C elements are not used in
+      calculations (not multiplied by zero - just not referenced)
+    * if Alpha=0, A is not used (not multiplied by zero - just not referenced)
+    * if both Beta and Alpha are zero, C is filled by zeros.
+
+    INPUT PARAMETERS
+        M       -   matrix size, M>0
+        N       -   matrix size, N>0
+        K       -   matrix size, K>0
+        Alpha   -   coefficient
+        A       -   matrix
+        IA      -   submatrix offset
+        JA      -   submatrix offset
+        OpTypeA -   transformation type:
+                    * 0 - no transformation
+                    * 1 - transposition
+        B       -   matrix
+        IB      -   submatrix offset
+        JB      -   submatrix offset
+        OpTypeB -   transformation type:
+                    * 0 - no transformation
+                    * 1 - transposition
+        Beta    -   coefficient
+        C       -   matrix
+        IC      -   submatrix offset
+        JC      -   submatrix offset
+
+      -- ALGLIB routine --
+         16.12.2009
+         Bochkanov Sergey
+    *************************************************************************/
+    public static void rmatrixgemm(int m, int n, int k, double alpha, double[,] a, int ia, int ja, int optypea, double[,] b, int ib, int jb, int optypeb, double beta, ref double[,] c, int ic, int jc)
+    {
+
+        ablas.rmatrixgemm(m, n, k, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, ref c, ic, jc);
+        return;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    QR decomposition of a rectangular matrix of size MxN
+
+    Input parameters:
+        A   -   matrix A whose indexes range within [0..M-1, 0..N-1].
+        M   -   number of rows in matrix A.
+        N   -   number of columns in matrix A.
+
+    Output parameters:
+        A   -   matrices Q and R in compact form (see below).
+        Tau -   array of scalar factors which are used to form
+                matrix Q. Array whose index ranges within [0.. Min(M-1,N-1)].
+
+    Matrix A is represented as A = QR, where Q is an orthogonal matrix of size
+    MxM, R - upper triangular (or upper trapezoid) matrix of size M x N.
+
+    The elements of matrix R are located on and above the main diagonal of
+    matrix A. The elements which are located in Tau array and below the main
+    diagonal of matrix A are used to form matrix Q as follows:
+
+    Matrix Q is represented as a product of elementary reflections
+
+    Q = H(0)*H(2)*...*H(k-1),
+
+    where k = min(m,n), and each H(i) is in the form
+
+    H(i) = 1 - tau * v * (v^T)
+
+    where tau is a scalar stored in Tau[I]; v - real vector,
+    so that v(0:i-1) = 0, v(i) = 1, v(i+1:m-1) stored in A(i+1:m-1,i).
+
+      -- ALGLIB routine --
+         17.02.2010
+         Bochkanov Sergey
+    *************************************************************************/
+    public static void rmatrixqr(ref double[,] a, int m, int n, out double[] tau)
+    {
+        tau = new double[0];
+        ortfac.rmatrixqr(ref a, m, n, ref tau);
+        return;
+    }
+
+    /*************************************************************************
+    LQ decomposition of a rectangular matrix of size MxN
+
+    Input parameters:
+        A   -   matrix A whose indexes range within [0..M-1, 0..N-1].
+        M   -   number of rows in matrix A.
+        N   -   number of columns in matrix A.
+
+    Output parameters:
+        A   -   matrices L and Q in compact form (see below)
+        Tau -   array of scalar factors which are used to form
+                matrix Q. Array whose index ranges within [0..Min(M,N)-1].
+
+    Matrix A is represented as A = LQ, where Q is an orthogonal matrix of size
+    MxM, L - lower triangular (or lower trapezoid) matrix of size M x N.
+
+    The elements of matrix L are located on and below  the  main  diagonal  of
+    matrix A. The elements which are located in Tau array and above  the  main
+    diagonal of matrix A are used to form matrix Q as follows:
+
+    Matrix Q is represented as a product of elementary reflections
+
+    Q = H(k-1)*H(k-2)*...*H(1)*H(0),
+
+    where k = min(m,n), and each H(i) is of the form
+
+    H(i) = 1 - tau * v * (v^T)
+
+    where tau is a scalar stored in Tau[I]; v - real vector, so that v(0:i-1)=0,
+    v(i) = 1, v(i+1:n-1) stored in A(i,i+1:n-1).
+
+      -- ALGLIB routine --
+         17.02.2010
+         Bochkanov Sergey
+    *************************************************************************/
+    public static void rmatrixlq(ref double[,] a, int m, int n, out double[] tau)
+    {
+        tau = new double[0];
+        ortfac.rmatrixlq(ref a, m, n, ref tau);
+        return;
+    }
+
+    /*************************************************************************
+    QR decomposition of a rectangular complex matrix of size MxN
+
+    Input parameters:
+        A   -   matrix A whose indexes range within [0..M-1, 0..N-1]
+        M   -   number of rows in matrix A.
+        N   -   number of columns in matrix A.
+
+    Output parameters:
+        A   -   matrices Q and R in compact form
+        Tau -   array of scalar factors which are used to form matrix Q. Array
+                whose indexes range within [0.. Min(M,N)-1]
+
+    Matrix A is represented as A = QR, where Q is an orthogonal matrix of size
+    MxM, R - upper triangular (or upper trapezoid) matrix of size MxN.
+
+      -- LAPACK routine (version 3.0) --
+         Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd.,
+         Courant Institute, Argonne National Lab, and Rice University
+         September 30, 1994
+    *************************************************************************/
+    public static void cmatrixqr(ref complex[,] a, int m, int n, out complex[] tau)
+    {
+        tau = new complex[0];
+        ortfac.cmatrixqr(ref a, m, n, ref tau);
+        return;
+    }
+
+    /*************************************************************************
+    LQ decomposition of a rectangular complex matrix of size MxN
+
+    Input parameters:
+        A   -   matrix A whose indexes range within [0..M-1, 0..N-1]
+        M   -   number of rows in matrix A.
+        N   -   number of columns in matrix A.
+
+    Output parameters:
+        A   -   matrices Q and L in compact form
+        Tau -   array of scalar factors which are used to form matrix Q. Array
+                whose indexes range within [0.. Min(M,N)-1]
+
+    Matrix A is represented as A = LQ, where Q is an orthogonal matrix of size
+    MxM, L - lower triangular (or lower trapezoid) matrix of size MxN.
+
+      -- LAPACK routine (version 3.0) --
+         Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd.,
+         Courant Institute, Argonne National Lab, and Rice University
+         September 30, 1994
+    *************************************************************************/
+    public static void cmatrixlq(ref complex[,] a, int m, int n, out complex[] tau)
+    {
+        tau = new complex[0];
+        ortfac.cmatrixlq(ref a, m, n, ref tau);
+        return;
+    }
+
+    /*************************************************************************
+    Partial unpacking of matrix Q from the QR decomposition of a matrix A
+
+    Input parameters:
+        A       -   matrices Q and R in compact form.
+                    Output of RMatrixQR subroutine.
+        M       -   number of rows in given matrix A. M>=0.
+        N       -   number of columns in given matrix A. N>=0.
+        Tau     -   scalar factors which are used to form Q.
+                    Output of the RMatrixQR subroutine.
+        QColumns -  required number of columns of matrix Q. M>=QColumns>=0.
+
+    Output parameters:
+        Q       -   first QColumns columns of matrix Q.
+                    Array whose indexes range within [0..M-1, 0..QColumns-1].
+                    If QColumns=0, the array remains unchanged.
+
+      -- ALGLIB routine --
+         17.02.2010
+         Bochkanov Sergey
+    *************************************************************************/
+    public static void rmatrixqrunpackq(double[,] a, int m, int n, double[] tau, int qcolumns, out double[,] q)
+    {
+        q = new double[0,0];
+        ortfac.rmatrixqrunpackq(a, m, n, tau, qcolumns, ref q);
+        return;
+    }
+
+    /*************************************************************************
+    Unpacking of matrix R from the QR decomposition of a matrix A
+
+    Input parameters:
+        A       -   matrices Q and R in compact form.
+                    Output of RMatrixQR subroutine.
+        M       -   number of rows in given matrix A. M>=0.
+        N       -   number of columns in given matrix A. N>=0.
+
+    Output parameters:
+        R       -   matrix R, array[0..M-1, 0..N-1].
+
+      -- ALGLIB routine --
+         17.02.2010
+         Bochkanov Sergey
+    *************************************************************************/
+    public static void rmatrixqrunpackr(double[,] a, int m, int n, out double[,] r)
+    {
+        r = new double[0,0];
+        ortfac.rmatrixqrunpackr(a, m, n, ref r);
+        return;
+    }
+
+    /*************************************************************************
+    Partial unpacking of matrix Q from the LQ decomposition of a matrix A
+
+    Input parameters:
+        A       -   matrices L and Q in compact form.
+                    Output of RMatrixLQ subroutine.
+        M       -   number of rows in given matrix A. M>=0.
+        N       -   number of columns in given matrix A. N>=0.
+        Tau     -   scalar factors which are used to form Q.
+                    Output of the RMatrixLQ subroutine.
+        QRows   -   required number of rows in matrix Q. N>=QRows>=0.
+
+    Output parameters:
+        Q       -   first QRows rows of matrix Q. Array whose indexes range
+                    within [0..QRows-1, 0..N-1]. If QRows=0, the array remains
+                    unchanged.
+
+      -- ALGLIB routine --
+         17.02.2010
+         Bochkanov Sergey
+    *************************************************************************/
+    public static void rmatrixlqunpackq(double[,] a, int m, int n, double[] tau, int qrows, out double[,] q)
+    {
+        q = new double[0,0];
+        ortfac.rmatrixlqunpackq(a, m, n, tau, qrows, ref q);
+        return;
+    }
+
+    /*************************************************************************
+    Unpacking of matrix L from the LQ decomposition of a matrix A
+
+    Input parameters:
+        A       -   matrices Q and L in compact form.
+                    Output of RMatrixLQ subroutine.
+        M       -   number of rows in given matrix A. M>=0.
+        N       -   number of columns in given matrix A. N>=0.
+
+    Output parameters:
+        L       -   matrix L, array[0..M-1, 0..N-1].
+
+      -- ALGLIB routine --
+         17.02.2010
+         Bochkanov Sergey
+    *************************************************************************/
+    public static void rmatrixlqunpackl(double[,] a, int m, int n, out double[,] l)
+    {
+        l = new double[0,0];
+        ortfac.rmatrixlqunpackl(a, m, n, ref l);
+        return;
+    }
+
+    /*************************************************************************
+    Partial unpacking of matrix Q from QR decomposition of a complex matrix A.
+
+    Input parameters:
+        A           -   matrices Q and R in compact form.
+                        Output of CMatrixQR subroutine .
+        M           -   number of rows in matrix A. M>=0.
+        N           -   number of columns in matrix A. N>=0.
+        Tau         -   scalar factors which are used to form Q.
+                        Output of CMatrixQR subroutine .
+        QColumns    -   required number of columns in matrix Q. M>=QColumns>=0.
+
+    Output parameters:
+        Q           -   first QColumns columns of matrix Q.
+                        Array whose index ranges within [0..M-1, 0..QColumns-1].
+                        If QColumns=0, array isn't changed.
+
+      -- ALGLIB routine --
+         17.02.2010
+         Bochkanov Sergey
+    *************************************************************************/
+    public static void cmatrixqrunpackq(complex[,] a, int m, int n, complex[] tau, int qcolumns, out complex[,] q)
+    {
+        q = new complex[0,0];
+        ortfac.cmatrixqrunpackq(a, m, n, tau, qcolumns, ref q);
+        return;
+    }
+
+    /*************************************************************************
+    Unpacking of matrix R from the QR decomposition of a matrix A
+
+    Input parameters:
+        A       -   matrices Q and R in compact form.
+                    Output of CMatrixQR subroutine.
+        M       -   number of rows in given matrix A. M>=0.
+        N       -   number of columns in given matrix A. N>=0.
+
+    Output parameters:
+        R       -   matrix R, array[0..M-1, 0..N-1].
+
+      -- ALGLIB routine --
+         17.02.2010
+         Bochkanov Sergey
+    *************************************************************************/
+    public static void cmatrixqrunpackr(complex[,] a, int m, int n, out complex[,] r)
+    {
+        r = new complex[0,0];
+        ortfac.cmatrixqrunpackr(a, m, n, ref r);
+        return;
+    }
+
+    /*************************************************************************
+    Partial unpacking of matrix Q from LQ decomposition of a complex matrix A.
+
+    Input parameters:
+        A           -   matrices Q and R in compact form.
+                        Output of CMatrixLQ subroutine .
+        M           -   number of rows in matrix A. M>=0.
+        N           -   number of columns in matrix A. N>=0.
+        Tau         -   scalar factors which are used to form Q.
+                        Output of CMatrixLQ subroutine .
+        QRows       -   required number of rows in matrix Q. N>=QColumns>=0.
+
+    Output parameters:
+        Q           -   first QRows rows of matrix Q.
+                        Array whose index ranges within [0..QRows-1, 0..N-1].
+                        If QRows=0, array isn't changed.
+
+      -- ALGLIB routine --
+         17.02.2010
+         Bochkanov Sergey
+    *************************************************************************/
+    public static void cmatrixlqunpackq(complex[,] a, int m, int n, complex[] tau, int qrows, out complex[,] q)
+    {
+        q = new complex[0,0];
+        ortfac.cmatrixlqunpackq(a, m, n, tau, qrows, ref q);
+        return;
+    }
+
+    /*************************************************************************
+    Unpacking of matrix L from the LQ decomposition of a matrix A
+
+    Input parameters:
+        A       -   matrices Q and L in compact form.
+                    Output of CMatrixLQ subroutine.
+        M       -   number of rows in given matrix A. M>=0.
+        N       -   number of columns in given matrix A. N>=0.
+
+    Output parameters:
+        L       -   matrix L, array[0..M-1, 0..N-1].
+
+      -- ALGLIB routine --
+         17.02.2010
+         Bochkanov Sergey
+    *************************************************************************/
+    public static void cmatrixlqunpackl(complex[,] a, int m, int n, out complex[,] l)
+    {
+        l = new complex[0,0];
+        ortfac.cmatrixlqunpackl(a, m, n, ref l);
+        return;
+    }
+
+    /*************************************************************************
+    Reduction of a rectangular matrix to  bidiagonal form
+
+    The algorithm reduces the rectangular matrix A to  bidiagonal form by
+    orthogonal transformations P and Q: A = Q*B*P.
+
+    Input parameters:
+        A       -   source matrix. array[0..M-1, 0..N-1]
+        M       -   number of rows in matrix A.
+        N       -   number of columns in matrix A.
+
+    Output parameters:
+        A       -   matrices Q, B, P in compact form (see below).
+        TauQ    -   scalar factors which are used to form matrix Q.
+        TauP    -   scalar factors which are used to form matrix P.
+
+    The main diagonal and one of the  secondary  diagonals  of  matrix  A  are
+    replaced with bidiagonal  matrix  B.  Other  elements  contain  elementary
+    reflections which form MxM matrix Q and NxN matrix P, respectively.
+
+    If M>=N, B is the upper  bidiagonal  MxN  matrix  and  is  stored  in  the
+    corresponding  elements  of  matrix  A.  Matrix  Q  is  represented  as  a
+    product   of   elementary   reflections   Q = H(0)*H(1)*...*H(n-1),  where
+    H(i) = 1-tau*v*v'. Here tau is a scalar which is stored  in  TauQ[i],  and
+    vector v has the following  structure:  v(0:i-1)=0, v(i)=1, v(i+1:m-1)  is
+    stored   in   elements   A(i+1:m-1,i).   Matrix   P  is  as  follows:  P =
+    G(0)*G(1)*...*G(n-2), where G(i) = 1 - tau*u*u'. Tau is stored in TauP[i],
+    u(0:i)=0, u(i+1)=1, u(i+2:n-1) is stored in elements A(i,i+2:n-1).
+
+    If M<N, B is the  lower  bidiagonal  MxN  matrix  and  is  stored  in  the
+    corresponding   elements  of  matrix  A.  Q = H(0)*H(1)*...*H(m-2),  where
+    H(i) = 1 - tau*v*v', tau is stored in TauQ, v(0:i)=0, v(i+1)=1, v(i+2:m-1)
+    is    stored    in   elements   A(i+2:m-1,i).    P = G(0)*G(1)*...*G(m-1),
+    G(i) = 1-tau*u*u', tau is stored in  TauP,  u(0:i-1)=0, u(i)=1, u(i+1:n-1)
+    is stored in A(i,i+1:n-1).
+
+    EXAMPLE:
+
+    m=6, n=5 (m > n):               m=5, n=6 (m < n):
+
+    (  d   e   u1  u1  u1 )         (  d   u1  u1  u1  u1  u1 )
+    (  v1  d   e   u2  u2 )         (  e   d   u2  u2  u2  u2 )
+    (  v1  v2  d   e   u3 )         (  v1  e   d   u3  u3  u3 )
+    (  v1  v2  v3  d   e  )         (  v1  v2  e   d   u4  u4 )
+    (  v1  v2  v3  v4  d  )         (  v1  v2  v3  e   d   u5 )
+    (  v1  v2  v3  v4  v5 )
+
+    Here vi and ui are vectors which form H(i) and G(i), and d and e -
+    are the diagonal and off-diagonal elements of matrix B.
+
+      -- LAPACK routine (version 3.0) --
+         Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd.,
+         Courant Institute, Argonne National Lab, and Rice University
+         September 30, 1994.
+         Sergey Bochkanov, ALGLIB project, translation from FORTRAN to
+         pseudocode, 2007-2010.
+    *************************************************************************/
+    public static void rmatrixbd(ref double[,] a, int m, int n, out double[] tauq, out double[] taup)
+    {
+        tauq = new double[0];
+        taup = new double[0];
+        ortfac.rmatrixbd(ref a, m, n, ref tauq, ref taup);
+        return;
+    }
+
+    /*************************************************************************
+    Unpacking matrix Q which reduces a matrix to bidiagonal form.
+
+    Input parameters:
+        QP          -   matrices Q and P in compact form.
+                        Output of ToBidiagonal subroutine.
+        M           -   number of rows in matrix A.
+        N           -   number of columns in matrix A.
+        TAUQ        -   scalar factors which are used to form Q.
+                        Output of ToBidiagonal subroutine.
+        QColumns    -   required number of columns in matrix Q.
+                        M>=QColumns>=0.
+
+    Output parameters:
+        Q           -   first QColumns columns of matrix Q.
+                        Array[0..M-1, 0..QColumns-1]
+                        If QColumns=0, the array is not modified.
+
+      -- ALGLIB --
+         2005-2010
+         Bochkanov Sergey
+    *************************************************************************/
+    public static void rmatrixbdunpackq(double[,] qp, int m, int n, double[] tauq, int qcolumns, out double[,] q)
+    {
+        q = new double[0,0];
+        ortfac.rmatrixbdunpackq(qp, m, n, tauq, qcolumns, ref q);
+        return;
+    }
+
+    /*************************************************************************
+    Multiplication by matrix Q which reduces matrix A to  bidiagonal form.
+
+    The algorithm allows pre- or post-multiply by Q or Q'.
+
+    Input parameters:
+        QP          -   matrices Q and P in compact form.
+                        Output of ToBidiagonal subroutine.
+        M           -   number of rows in matrix A.
+        N           -   number of columns in matrix A.
+        TAUQ        -   scalar factors which are used to form Q.
+                        Output of ToBidiagonal subroutine.
+        Z           -   multiplied matrix.
+                        array[0..ZRows-1,0..ZColumns-1]
+        ZRows       -   number of rows in matrix Z. If FromTheRight=False,
+                        ZRows=M, otherwise ZRows can be arbitrary.
+        ZColumns    -   number of columns in matrix Z. If FromTheRight=True,
+                        ZColumns=M, otherwise ZColumns can be arbitrary.
+        FromTheRight -  pre- or post-multiply.
+        DoTranspose -   multiply by Q or Q'.
+
+    Output parameters:
+        Z           -   product of Z and Q.
+                        Array[0..ZRows-1,0..ZColumns-1]
+                        If ZRows=0 or ZColumns=0, the array is not modified.
+
+      -- ALGLIB --
+         2005-2010
+         Bochkanov Sergey
+    *************************************************************************/
+    public static void rmatrixbdmultiplybyq(double[,] qp, int m, int n, double[] tauq, ref double[,] z, int zrows, int zcolumns, bool fromtheright, bool dotranspose)
+    {
+
+        ortfac.rmatrixbdmultiplybyq(qp, m, n, tauq, ref z, zrows, zcolumns, fromtheright, dotranspose);
+        return;
+    }
+
+    /*************************************************************************
+    Unpacking matrix P which reduces matrix A to bidiagonal form.
+    The subroutine returns transposed matrix P.
+
+    Input parameters:
+        QP      -   matrices Q and P in compact form.
+                    Output of ToBidiagonal subroutine.
+        M       -   number of rows in matrix A.
+        N       -   number of columns in matrix A.
+        TAUP    -   scalar factors which are used to form P.
+                    Output of ToBidiagonal subroutine.
+        PTRows  -   required number of rows of matrix P^T. N >= PTRows >= 0.
+
+    Output parameters:
+        PT      -   first PTRows columns of matrix P^T
+                    Array[0..PTRows-1, 0..N-1]
+                    If PTRows=0, the array is not modified.
+
+      -- ALGLIB --
+         2005-2010
+         Bochkanov Sergey
+    *************************************************************************/
+    public static void rmatrixbdunpackpt(double[,] qp, int m, int n, double[] taup, int ptrows, out double[,] pt)
+    {
+        pt = new double[0,0];
+        ortfac.rmatrixbdunpackpt(qp, m, n, taup, ptrows, ref pt);
+        return;
+    }
+
+    /*************************************************************************
+    Multiplication by matrix P which reduces matrix A to  bidiagonal form.
+
+    The algorithm allows pre- or post-multiply by P or P'.
+
+    Input parameters:
+        QP          -   matrices Q and P in compact form.
+                        Output of RMatrixBD subroutine.
+        M           -   number of rows in matrix A.
+        N           -   number of columns in matrix A.
+        TAUP        -   scalar factors which are used to form P.
+                        Output of RMatrixBD subroutine.
+        Z           -   multiplied matrix.
+                        Array whose indexes range within [0..ZRows-1,0..ZColumns-1].
+        ZRows       -   number of rows in matrix Z. If FromTheRight=False,
+                        ZRows=N, otherwise ZRows can be arbitrary.
+        ZColumns    -   number of columns in matrix Z. If FromTheRight=True,
+                        ZColumns=N, otherwise ZColumns can be arbitrary.
+        FromTheRight -  pre- or post-multiply.
+        DoTranspose -   multiply by P or P'.
+
+    Output parameters:
+        Z - product of Z and P.
+                    Array whose indexes range within [0..ZRows-1,0..ZColumns-1].
+                    If ZRows=0 or ZColumns=0, the array is not modified.
+
+      -- ALGLIB --
+         2005-2010
+         Bochkanov Sergey
+    *************************************************************************/
+    public static void rmatrixbdmultiplybyp(double[,] qp, int m, int n, double[] taup, ref double[,] z, int zrows, int zcolumns, bool fromtheright, bool dotranspose)
+    {
+
+        ortfac.rmatrixbdmultiplybyp(qp, m, n, taup, ref z, zrows, zcolumns, fromtheright, dotranspose);
+        return;
+    }
+
+    /*************************************************************************
+    Unpacking of the main and secondary diagonals of bidiagonal decomposition
+    of matrix A.
+
+    Input parameters:
+        B   -   output of RMatrixBD subroutine.
+        M   -   number of rows in matrix B.
+        N   -   number of columns in matrix B.
+
+    Output parameters:
+        IsUpper -   True, if the matrix is upper bidiagonal.
+                    otherwise IsUpper is False.
+        D       -   the main diagonal.
+                    Array whose index ranges within [0..Min(M,N)-1].
+        E       -   the secondary diagonal (upper or lower, depending on
+                    the value of IsUpper).
+                    Array index ranges within [0..Min(M,N)-1], the last
+                    element is not used.
+
+      -- ALGLIB --
+         2005-2010
+         Bochkanov Sergey
+    *************************************************************************/
+    public static void rmatrixbdunpackdiagonals(double[,] b, int m, int n, out bool isupper, out double[] d, out double[] e)
+    {
+        isupper = false;
+        d = new double[0];
+        e = new double[0];
+        ortfac.rmatrixbdunpackdiagonals(b, m, n, ref isupper, ref d, ref e);
+        return;
+    }
+
+    /*************************************************************************
+    Reduction of a square matrix to  upper Hessenberg form: Q'*A*Q = H,
+    where Q is an orthogonal matrix, H - Hessenberg matrix.
+
+    Input parameters:
+        A       -   matrix A with elements [0..N-1, 0..N-1]
+        N       -   size of matrix A.
+
+    Output parameters:
+        A       -   matrices Q and P in  compact form (see below).
+        Tau     -   array of scalar factors which are used to form matrix Q.
+                    Array whose index ranges within [0..N-2]
+
+    Matrix H is located on the main diagonal, on the lower secondary  diagonal
+    and above the main diagonal of matrix A. The elements which are used to
+    form matrix Q are situated in array Tau and below the lower secondary
+    diagonal of matrix A as follows:
+
+    Matrix Q is represented as a product of elementary reflections
+
+    Q = H(0)*H(2)*...*H(n-2),
+
+    where each H(i) is given by
+
+    H(i) = 1 - tau * v * (v^T)
+
+    where tau is a scalar stored in Tau[I]; v - is a real vector,
+    so that v(0:i) = 0, v(i+1) = 1, v(i+2:n-1) stored in A(i+2:n-1,i).
+
+      -- LAPACK routine (version 3.0) --
+         Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd.,
+         Courant Institute, Argonne National Lab, and Rice University
+         October 31, 1992
+    *************************************************************************/
+    public static void rmatrixhessenberg(ref double[,] a, int n, out double[] tau)
+    {
+        tau = new double[0];
+        ortfac.rmatrixhessenberg(ref a, n, ref tau);
+        return;
+    }
+
+    /*************************************************************************
+    Unpacking matrix Q which reduces matrix A to upper Hessenberg form
+
+    Input parameters:
+        A   -   output of RMatrixHessenberg subroutine.
+        N   -   size of matrix A.
+        Tau -   scalar factors which are used to form Q.
+                Output of RMatrixHessenberg subroutine.
+
+    Output parameters:
+        Q   -   matrix Q.
+                Array whose indexes range within [0..N-1, 0..N-1].
+
+      -- ALGLIB --
+         2005-2010
+         Bochkanov Sergey
+    *************************************************************************/
+    public static void rmatrixhessenbergunpackq(double[,] a, int n, double[] tau, out double[,] q)
+    {
+        q = new double[0,0];
+        ortfac.rmatrixhessenbergunpackq(a, n, tau, ref q);
+        return;
+    }
+
+    /*************************************************************************
+    Unpacking matrix H (the result of matrix A reduction to upper Hessenberg form)
+
+    Input parameters:
+        A   -   output of RMatrixHessenberg subroutine.
+        N   -   size of matrix A.
+
+    Output parameters:
+        H   -   matrix H. Array whose indexes range within [0..N-1, 0..N-1].
+
+      -- ALGLIB --
+         2005-2010
+         Bochkanov Sergey
+    *************************************************************************/
+    public static void rmatrixhessenbergunpackh(double[,] a, int n, out double[,] h)
+    {
+        h = new double[0,0];
+        ortfac.rmatrixhessenbergunpackh(a, n, ref h);
+        return;
+    }
+
+    /*************************************************************************
+    Reduction of a symmetric matrix which is given by its higher or lower
+    triangular part to a tridiagonal matrix using orthogonal similarity
+    transformation: Q'*A*Q=T.
+
+    Input parameters:
+        A       -   matrix to be transformed
+                    array with elements [0..N-1, 0..N-1].
+        N       -   size of matrix A.
+        IsUpper -   storage format. If IsUpper = True, then matrix A is given
+                    by its upper triangle, and the lower triangle is not used
+                    and not modified by the algorithm, and vice versa
+                    if IsUpper = False.
+
+    Output parameters:
+        A       -   matrices T and Q in  compact form (see lower)
+        Tau     -   array of factors which are forming matrices H(i)
+                    array with elements [0..N-2].
+        D       -   main diagonal of symmetric matrix T.
+                    array with elements [0..N-1].
+        E       -   secondary diagonal of symmetric matrix T.
+                    array with elements [0..N-2].
+
+
+      If IsUpper=True, the matrix Q is represented as a product of elementary
+      reflectors
+
+         Q = H(n-2) . . . H(2) H(0).
+
+      Each H(i) has the form
+
+         H(i) = I - tau * v * v'
+
+      where tau is a real scalar, and v is a real vector with
+      v(i+1:n-1) = 0, v(i) = 1, v(0:i-1) is stored on exit in
+      A(0:i-1,i+1), and tau in TAU(i).
+
+      If IsUpper=False, the matrix Q is represented as a product of elementary
+      reflectors
+
+         Q = H(0) H(2) . . . H(n-2).
+
+      Each H(i) has the form
+
+         H(i) = I - tau * v * v'
+
+      where tau is a real scalar, and v is a real vector with
+      v(0:i) = 0, v(i+1) = 1, v(i+2:n-1) is stored on exit in A(i+2:n-1,i),
+      and tau in TAU(i).
+
+      The contents of A on exit are illustrated by the following examples
+      with n = 5:
+
+      if UPLO = 'U':                       if UPLO = 'L':
+
+        (  d   e   v1  v2  v3 )              (  d                  )
+        (      d   e   v2  v3 )              (  e   d              )
+        (          d   e   v3 )              (  v0  e   d          )
+        (              d   e  )              (  v0  v1  e   d      )
+        (                  d  )              (  v0  v1  v2  e   d  )
+
+      where d and e denote diagonal and off-diagonal elements of T, and vi
+      denotes an element of the vector defining H(i).
+
+      -- LAPACK routine (version 3.0) --
+         Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd.,
+         Courant Institute, Argonne National Lab, and Rice University
+         October 31, 1992
+    *************************************************************************/
+    public static void smatrixtd(ref double[,] a, int n, bool isupper, out double[] tau, out double[] d, out double[] e)
+    {
+        tau = new double[0];
+        d = new double[0];
+        e = new double[0];
+        ortfac.smatrixtd(ref a, n, isupper, ref tau, ref d, ref e);
+        return;
+    }
+
+    /*************************************************************************
+    Unpacking matrix Q which reduces symmetric matrix to a tridiagonal
+    form.
+
+    Input parameters:
+        A       -   the result of a SMatrixTD subroutine
+        N       -   size of matrix A.
+        IsUpper -   storage format (a parameter of SMatrixTD subroutine)
+        Tau     -   the result of a SMatrixTD subroutine
+
+    Output parameters:
+        Q       -   transformation matrix.
+                    array with elements [0..N-1, 0..N-1].
+
+      -- ALGLIB --
+         Copyright 2005-2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void smatrixtdunpackq(double[,] a, int n, bool isupper, double[] tau, out double[,] q)
+    {
+        q = new double[0,0];
+        ortfac.smatrixtdunpackq(a, n, isupper, tau, ref q);
+        return;
+    }
+
+    /*************************************************************************
+    Reduction of a Hermitian matrix which is given  by  its  higher  or  lower
+    triangular part to a real  tridiagonal  matrix  using  unitary  similarity
+    transformation: Q'*A*Q = T.
+
+    Input parameters:
+        A       -   matrix to be transformed
+                    array with elements [0..N-1, 0..N-1].
+        N       -   size of matrix A.
+        IsUpper -   storage format. If IsUpper = True, then matrix A is  given
+                    by its upper triangle, and the lower triangle is not  used
+                    and not modified by the algorithm, and vice versa
+                    if IsUpper = False.
+
+    Output parameters:
+        A       -   matrices T and Q in  compact form (see lower)
+        Tau     -   array of factors which are forming matrices H(i)
+                    array with elements [0..N-2].
+        D       -   main diagonal of real symmetric matrix T.
+                    array with elements [0..N-1].
+        E       -   secondary diagonal of real symmetric matrix T.
+                    array with elements [0..N-2].
+
+
+      If IsUpper=True, the matrix Q is represented as a product of elementary
+      reflectors
+
+         Q = H(n-2) . . . H(2) H(0).
+
+      Each H(i) has the form
+
+         H(i) = I - tau * v * v'
+
+      where tau is a complex scalar, and v is a complex vector with
+      v(i+1:n-1) = 0, v(i) = 1, v(0:i-1) is stored on exit in
+      A(0:i-1,i+1), and tau in TAU(i).
+
+      If IsUpper=False, the matrix Q is represented as a product of elementary
+      reflectors
+
+         Q = H(0) H(2) . . . H(n-2).
+
+      Each H(i) has the form
+
+         H(i) = I - tau * v * v'
+
+      where tau is a complex scalar, and v is a complex vector with
+      v(0:i) = 0, v(i+1) = 1, v(i+2:n-1) is stored on exit in A(i+2:n-1,i),
+      and tau in TAU(i).
+
+      The contents of A on exit are illustrated by the following examples
+      with n = 5:
+
+      if UPLO = 'U':                       if UPLO = 'L':
+
+        (  d   e   v1  v2  v3 )              (  d                  )
+        (      d   e   v2  v3 )              (  e   d              )
+        (          d   e   v3 )              (  v0  e   d          )
+        (              d   e  )              (  v0  v1  e   d      )
+        (                  d  )              (  v0  v1  v2  e   d  )
+
+    where d and e denote diagonal and off-diagonal elements of T, and vi
+    denotes an element of the vector defining H(i).
+
+      -- LAPACK routine (version 3.0) --
+         Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd.,
+         Courant Institute, Argonne National Lab, and Rice University
+         October 31, 1992
+    *************************************************************************/
+    public static void hmatrixtd(ref complex[,] a, int n, bool isupper, out complex[] tau, out double[] d, out double[] e)
+    {
+        tau = new complex[0];
+        d = new double[0];
+        e = new double[0];
+        ortfac.hmatrixtd(ref a, n, isupper, ref tau, ref d, ref e);
+        return;
+    }
+
+    /*************************************************************************
+    Unpacking matrix Q which reduces a Hermitian matrix to a real  tridiagonal
+    form.
+
+    Input parameters:
+        A       -   the result of a HMatrixTD subroutine
+        N       -   size of matrix A.
+        IsUpper -   storage format (a parameter of HMatrixTD subroutine)
+        Tau     -   the result of a HMatrixTD subroutine
+
+    Output parameters:
+        Q       -   transformation matrix.
+                    array with elements [0..N-1, 0..N-1].
+
+      -- ALGLIB --
+         Copyright 2005-2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void hmatrixtdunpackq(complex[,] a, int n, bool isupper, complex[] tau, out complex[,] q)
+    {
+        q = new complex[0,0];
+        ortfac.hmatrixtdunpackq(a, n, isupper, tau, ref q);
+        return;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    Singular value decomposition of a bidiagonal matrix (extended algorithm)
+
+    The algorithm performs the singular value decomposition  of  a  bidiagonal
+    matrix B (upper or lower) representing it as B = Q*S*P^T, where Q and  P -
+    orthogonal matrices, S - diagonal matrix with non-negative elements on the
+    main diagonal, in descending order.
+
+    The  algorithm  finds  singular  values.  In  addition,  the algorithm can
+    calculate  matrices  Q  and P (more precisely, not the matrices, but their
+    product  with  given  matrices U and VT - U*Q and (P^T)*VT)).  Of  course,
+    matrices U and VT can be of any type, including identity. Furthermore, the
+    algorithm can calculate Q'*C (this product is calculated more  effectively
+    than U*Q,  because  this calculation operates with rows instead  of matrix
+    columns).
+
+    The feature of the algorithm is its ability to find  all  singular  values
+    including those which are arbitrarily close to 0  with  relative  accuracy
+    close to  machine precision. If the parameter IsFractionalAccuracyRequired
+    is set to True, all singular values will have high relative accuracy close
+    to machine precision. If the parameter is set to False, only  the  biggest
+    singular value will have relative accuracy  close  to  machine  precision.
+    The absolute error of other singular values is equal to the absolute error
+    of the biggest singular value.
+
+    Input parameters:
+        D       -   main diagonal of matrix B.
+                    Array whose index ranges within [0..N-1].
+        E       -   superdiagonal (or subdiagonal) of matrix B.
+                    Array whose index ranges within [0..N-2].
+        N       -   size of matrix B.
+        IsUpper -   True, if the matrix is upper bidiagonal.
+        IsFractionalAccuracyRequired -
+                    THIS PARAMETER IS IGNORED SINCE ALGLIB 3.5.0
+                    SINGULAR VALUES ARE ALWAYS SEARCHED WITH HIGH ACCURACY.
+        U       -   matrix to be multiplied by Q.
+                    Array whose indexes range within [0..NRU-1, 0..N-1].
+                    The matrix can be bigger, in that case only the  submatrix
+                    [0..NRU-1, 0..N-1] will be multiplied by Q.
+        NRU     -   number of rows in matrix U.
+        C       -   matrix to be multiplied by Q'.
+                    Array whose indexes range within [0..N-1, 0..NCC-1].
+                    The matrix can be bigger, in that case only the  submatrix
+                    [0..N-1, 0..NCC-1] will be multiplied by Q'.
+        NCC     -   number of columns in matrix C.
+        VT      -   matrix to be multiplied by P^T.
+                    Array whose indexes range within [0..N-1, 0..NCVT-1].
+                    The matrix can be bigger, in that case only the  submatrix
+                    [0..N-1, 0..NCVT-1] will be multiplied by P^T.
+        NCVT    -   number of columns in matrix VT.
+
+    Output parameters:
+        D       -   singular values of matrix B in descending order.
+        U       -   if NRU>0, contains matrix U*Q.
+        VT      -   if NCVT>0, contains matrix (P^T)*VT.
+        C       -   if NCC>0, contains matrix Q'*C.
+
+    Result:
+        True, if the algorithm has converged.
+        False, if the algorithm hasn't converged (rare case).
+
+    Additional information:
+        The type of convergence is controlled by the internal  parameter  TOL.
+        If the parameter is greater than 0, the singular values will have
+        relative accuracy TOL. If TOL<0, the singular values will have
+        absolute accuracy ABS(TOL)*norm(B).
+        By default, |TOL| falls within the range of 10*Epsilon and 100*Epsilon,
+        where Epsilon is the machine precision. It is not  recommended  to  use
+        TOL less than 10*Epsilon since this will  considerably  slow  down  the
+        algorithm and may not lead to error decreasing.
+    History:
+        * 31 March, 2007.
+            changed MAXITR from 6 to 12.
+
+      -- LAPACK routine (version 3.0) --
+         Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd.,
+         Courant Institute, Argonne National Lab, and Rice University
+         October 31, 1999.
+    *************************************************************************/
+    public static bool rmatrixbdsvd(ref double[] d, double[] e, int n, bool isupper, bool isfractionalaccuracyrequired, ref double[,] u, int nru, ref double[,] c, int ncc, ref double[,] vt, int ncvt)
+    {
+
+        bool result = bdsvd.rmatrixbdsvd(ref d, e, n, isupper, isfractionalaccuracyrequired, ref u, nru, ref c, ncc, ref vt, ncvt);
+        return result;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    Singular value decomposition of a rectangular matrix.
+
+    The algorithm calculates the singular value decomposition of a matrix of
+    size MxN: A = U * S * V^T
+
+    The algorithm finds the singular values and, optionally, matrices U and V^T.
+    The algorithm can find both first min(M,N) columns of matrix U and rows of
+    matrix V^T (singular vectors), and matrices U and V^T wholly (of sizes MxM
+    and NxN respectively).
+
+    Take into account that the subroutine does not return matrix V but V^T.
+
+    Input parameters:
+        A           -   matrix to be decomposed.
+                        Array whose indexes range within [0..M-1, 0..N-1].
+        M           -   number of rows in matrix A.
+        N           -   number of columns in matrix A.
+        UNeeded     -   0, 1 or 2. See the description of the parameter U.
+        VTNeeded    -   0, 1 or 2. See the description of the parameter VT.
+        AdditionalMemory -
+                        If the parameter:
+                         * equals 0, the algorithm doesnt use additional
+                           memory (lower requirements, lower performance).
+                         * equals 1, the algorithm uses additional
+                           memory of size min(M,N)*min(M,N) of real numbers.
+                           It often speeds up the algorithm.
+                         * equals 2, the algorithm uses additional
+                           memory of size M*min(M,N) of real numbers.
+                           It allows to get a maximum performance.
+                        The recommended value of the parameter is 2.
+
+    Output parameters:
+        W           -   contains singular values in descending order.
+        U           -   if UNeeded=0, U isn't changed, the left singular vectors
+                        are not calculated.
+                        if Uneeded=1, U contains left singular vectors (first
+                        min(M,N) columns of matrix U). Array whose indexes range
+                        within [0..M-1, 0..Min(M,N)-1].
+                        if UNeeded=2, U contains matrix U wholly. Array whose
+                        indexes range within [0..M-1, 0..M-1].
+        VT          -   if VTNeeded=0, VT isnt changed, the right singular vectors
+                        are not calculated.
+                        if VTNeeded=1, VT contains right singular vectors (first
+                        min(M,N) rows of matrix V^T). Array whose indexes range
+                        within [0..min(M,N)-1, 0..N-1].
+                        if VTNeeded=2, VT contains matrix V^T wholly. Array whose
+                        indexes range within [0..N-1, 0..N-1].
+
+      -- ALGLIB --
+         Copyright 2005 by Bochkanov Sergey
+    *************************************************************************/
+    public static bool rmatrixsvd(double[,] a, int m, int n, int uneeded, int vtneeded, int additionalmemory, out double[] w, out double[,] u, out double[,] vt)
+    {
+        w = new double[0];
+        u = new double[0,0];
+        vt = new double[0,0];
+        bool result = svd.rmatrixsvd(a, m, n, uneeded, vtneeded, additionalmemory, ref w, ref u, ref vt);
+        return result;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    Finding the eigenvalues and eigenvectors of a symmetric matrix
+
+    The algorithm finds eigen pairs of a symmetric matrix by reducing it to
+    tridiagonal form and using the QL/QR algorithm.
+
+    Input parameters:
+        A       -   symmetric matrix which is given by its upper or lower
+                    triangular part.
+                    Array whose indexes range within [0..N-1, 0..N-1].
+        N       -   size of matrix A.
+        ZNeeded -   flag controlling whether the eigenvectors are needed or not.
+                    If ZNeeded is equal to:
+                     * 0, the eigenvectors are not returned;
+                     * 1, the eigenvectors are returned.
+        IsUpper -   storage format.
+
+    Output parameters:
+        D       -   eigenvalues in ascending order.
+                    Array whose index ranges within [0..N-1].
+        Z       -   if ZNeeded is equal to:
+                     * 0, Z hasnt changed;
+                     * 1, Z contains the eigenvectors.
+                    Array whose indexes range within [0..N-1, 0..N-1].
+                    The eigenvectors are stored in the matrix columns.
+
+    Result:
+        True, if the algorithm has converged.
+        False, if the algorithm hasn't converged (rare case).
+
+      -- ALGLIB --
+         Copyright 2005-2008 by Bochkanov Sergey
+    *************************************************************************/
+    public static bool smatrixevd(double[,] a, int n, int zneeded, bool isupper, out double[] d, out double[,] z)
+    {
+        d = new double[0];
+        z = new double[0,0];
+        bool result = evd.smatrixevd(a, n, zneeded, isupper, ref d, ref z);
+        return result;
+    }
+
+    /*************************************************************************
+    Subroutine for finding the eigenvalues (and eigenvectors) of  a  symmetric
+    matrix  in  a  given half open interval (A, B] by using  a  bisection  and
+    inverse iteration
+
+    Input parameters:
+        A       -   symmetric matrix which is given by its upper or lower
+                    triangular part. Array [0..N-1, 0..N-1].
+        N       -   size of matrix A.
+        ZNeeded -   flag controlling whether the eigenvectors are needed or not.
+                    If ZNeeded is equal to:
+                     * 0, the eigenvectors are not returned;
+                     * 1, the eigenvectors are returned.
+        IsUpperA -  storage format of matrix A.
+        B1, B2 -    half open interval (B1, B2] to search eigenvalues in.
+
+    Output parameters:
+        M       -   number of eigenvalues found in a given half-interval (M>=0).
+        W       -   array of the eigenvalues found.
+                    Array whose index ranges within [0..M-1].
+        Z       -   if ZNeeded is equal to:
+                     * 0, Z hasnt changed;
+                     * 1, Z contains eigenvectors.
+                    Array whose indexes range within [0..N-1, 0..M-1].
+                    The eigenvectors are stored in the matrix columns.
+
+    Result:
+        True, if successful. M contains the number of eigenvalues in the given
+        half-interval (could be equal to 0), W contains the eigenvalues,
+        Z contains the eigenvectors (if needed).
+
+        False, if the bisection method subroutine wasn't able to find the
+        eigenvalues in the given interval or if the inverse iteration subroutine
+        wasn't able to find all the corresponding eigenvectors.
+        In that case, the eigenvalues and eigenvectors are not returned,
+        M is equal to 0.
+
+      -- ALGLIB --
+         Copyright 07.01.2006 by Bochkanov Sergey
+    *************************************************************************/
+    public static bool smatrixevdr(double[,] a, int n, int zneeded, bool isupper, double b1, double b2, out int m, out double[] w, out double[,] z)
+    {
+        m = 0;
+        w = new double[0];
+        z = new double[0,0];
+        bool result = evd.smatrixevdr(a, n, zneeded, isupper, b1, b2, ref m, ref w, ref z);
+        return result;
+    }
+
+    /*************************************************************************
+    Subroutine for finding the eigenvalues and  eigenvectors  of  a  symmetric
+    matrix with given indexes by using bisection and inverse iteration methods.
+
+    Input parameters:
+        A       -   symmetric matrix which is given by its upper or lower
+                    triangular part. Array whose indexes range within [0..N-1, 0..N-1].
+        N       -   size of matrix A.
+        ZNeeded -   flag controlling whether the eigenvectors are needed or not.
+                    If ZNeeded is equal to:
+                     * 0, the eigenvectors are not returned;
+                     * 1, the eigenvectors are returned.
+        IsUpperA -  storage format of matrix A.
+        I1, I2 -    index interval for searching (from I1 to I2).
+                    0 <= I1 <= I2 <= N-1.
+
+    Output parameters:
+        W       -   array of the eigenvalues found.
+                    Array whose index ranges within [0..I2-I1].
+        Z       -   if ZNeeded is equal to:
+                     * 0, Z hasnt changed;
+                     * 1, Z contains eigenvectors.
+                    Array whose indexes range within [0..N-1, 0..I2-I1].
+                    In that case, the eigenvectors are stored in the matrix columns.
+
+    Result:
+        True, if successful. W contains the eigenvalues, Z contains the
+        eigenvectors (if needed).
+
+        False, if the bisection method subroutine wasn't able to find the
+        eigenvalues in the given interval or if the inverse iteration subroutine
+        wasn't able to find all the corresponding eigenvectors.
+        In that case, the eigenvalues and eigenvectors are not returned.
+
+      -- ALGLIB --
+         Copyright 07.01.2006 by Bochkanov Sergey
+    *************************************************************************/
+    public static bool smatrixevdi(double[,] a, int n, int zneeded, bool isupper, int i1, int i2, out double[] w, out double[,] z)
+    {
+        w = new double[0];
+        z = new double[0,0];
+        bool result = evd.smatrixevdi(a, n, zneeded, isupper, i1, i2, ref w, ref z);
+        return result;
+    }
+
+    /*************************************************************************
+    Finding the eigenvalues and eigenvectors of a Hermitian matrix
+
+    The algorithm finds eigen pairs of a Hermitian matrix by  reducing  it  to
+    real tridiagonal form and using the QL/QR algorithm.
+
+    Input parameters:
+        A       -   Hermitian matrix which is given  by  its  upper  or  lower
+                    triangular part.
+                    Array whose indexes range within [0..N-1, 0..N-1].
+        N       -   size of matrix A.
+        IsUpper -   storage format.
+        ZNeeded -   flag controlling whether the eigenvectors  are  needed  or
+                    not. If ZNeeded is equal to:
+                     * 0, the eigenvectors are not returned;
+                     * 1, the eigenvectors are returned.
+
+    Output parameters:
+        D       -   eigenvalues in ascending order.
+                    Array whose index ranges within [0..N-1].
+        Z       -   if ZNeeded is equal to:
+                     * 0, Z hasnt changed;
+                     * 1, Z contains the eigenvectors.
+                    Array whose indexes range within [0..N-1, 0..N-1].
+                    The eigenvectors are stored in the matrix columns.
+
+    Result:
+        True, if the algorithm has converged.
+        False, if the algorithm hasn't converged (rare case).
+
+    Note:
+        eigenvectors of Hermitian matrix are defined up to  multiplication  by
+        a complex number L, such that |L|=1.
+
+      -- ALGLIB --
+         Copyright 2005, 23 March 2007 by Bochkanov Sergey
+    *************************************************************************/
+    public static bool hmatrixevd(complex[,] a, int n, int zneeded, bool isupper, out double[] d, out complex[,] z)
+    {
+        d = new double[0];
+        z = new complex[0,0];
+        bool result = evd.hmatrixevd(a, n, zneeded, isupper, ref d, ref z);
+        return result;
+    }
+
+    /*************************************************************************
+    Subroutine for finding the eigenvalues (and eigenvectors) of  a  Hermitian
+    matrix  in  a  given half-interval (A, B] by using a bisection and inverse
+    iteration
+
+    Input parameters:
+        A       -   Hermitian matrix which is given  by  its  upper  or  lower
+                    triangular  part.  Array  whose   indexes   range   within
+                    [0..N-1, 0..N-1].
+        N       -   size of matrix A.
+        ZNeeded -   flag controlling whether the eigenvectors  are  needed  or
+                    not. If ZNeeded is equal to:
+                     * 0, the eigenvectors are not returned;
+                     * 1, the eigenvectors are returned.
+        IsUpperA -  storage format of matrix A.
+        B1, B2 -    half-interval (B1, B2] to search eigenvalues in.
+
+    Output parameters:
+        M       -   number of eigenvalues found in a given half-interval, M>=0
+        W       -   array of the eigenvalues found.
+                    Array whose index ranges within [0..M-1].
+        Z       -   if ZNeeded is equal to:
+                     * 0, Z hasnt changed;
+                     * 1, Z contains eigenvectors.
+                    Array whose indexes range within [0..N-1, 0..M-1].
+                    The eigenvectors are stored in the matrix columns.
+
+    Result:
+        True, if successful. M contains the number of eigenvalues in the given
+        half-interval (could be equal to 0), W contains the eigenvalues,
+        Z contains the eigenvectors (if needed).
+
+        False, if the bisection method subroutine  wasn't  able  to  find  the
+        eigenvalues  in  the  given  interval  or  if  the  inverse  iteration
+        subroutine  wasn't  able  to  find all the corresponding eigenvectors.
+        In that case, the eigenvalues and eigenvectors are not returned, M  is
+        equal to 0.
+
+    Note:
+        eigen vectors of Hermitian matrix are defined up to multiplication  by
+        a complex number L, such as |L|=1.
+
+      -- ALGLIB --
+         Copyright 07.01.2006, 24.03.2007 by Bochkanov Sergey.
+    *************************************************************************/
+    public static bool hmatrixevdr(complex[,] a, int n, int zneeded, bool isupper, double b1, double b2, out int m, out double[] w, out complex[,] z)
+    {
+        m = 0;
+        w = new double[0];
+        z = new complex[0,0];
+        bool result = evd.hmatrixevdr(a, n, zneeded, isupper, b1, b2, ref m, ref w, ref z);
+        return result;
+    }
+
+    /*************************************************************************
+    Subroutine for finding the eigenvalues and  eigenvectors  of  a  Hermitian
+    matrix with given indexes by using bisection and inverse iteration methods
+
+    Input parameters:
+        A       -   Hermitian matrix which is given  by  its  upper  or  lower
+                    triangular part.
+                    Array whose indexes range within [0..N-1, 0..N-1].
+        N       -   size of matrix A.
+        ZNeeded -   flag controlling whether the eigenvectors  are  needed  or
+                    not. If ZNeeded is equal to:
+                     * 0, the eigenvectors are not returned;
+                     * 1, the eigenvectors are returned.
+        IsUpperA -  storage format of matrix A.
+        I1, I2 -    index interval for searching (from I1 to I2).
+                    0 <= I1 <= I2 <= N-1.
+
+    Output parameters:
+        W       -   array of the eigenvalues found.
+                    Array whose index ranges within [0..I2-I1].
+        Z       -   if ZNeeded is equal to:
+                     * 0, Z hasnt changed;
+                     * 1, Z contains eigenvectors.
+                    Array whose indexes range within [0..N-1, 0..I2-I1].
+                    In  that  case,  the eigenvectors are stored in the matrix
+                    columns.
+
+    Result:
+        True, if successful. W contains the eigenvalues, Z contains the
+        eigenvectors (if needed).
+
+        False, if the bisection method subroutine  wasn't  able  to  find  the
+        eigenvalues  in  the  given  interval  or  if  the  inverse  iteration
+        subroutine wasn't able to find  all  the  corresponding  eigenvectors.
+        In that case, the eigenvalues and eigenvectors are not returned.
+
+    Note:
+        eigen vectors of Hermitian matrix are defined up to multiplication  by
+        a complex number L, such as |L|=1.
+
+      -- ALGLIB --
+         Copyright 07.01.2006, 24.03.2007 by Bochkanov Sergey.
+    *************************************************************************/
+    public static bool hmatrixevdi(complex[,] a, int n, int zneeded, bool isupper, int i1, int i2, out double[] w, out complex[,] z)
+    {
+        w = new double[0];
+        z = new complex[0,0];
+        bool result = evd.hmatrixevdi(a, n, zneeded, isupper, i1, i2, ref w, ref z);
+        return result;
+    }
+
+    /*************************************************************************
+    Finding the eigenvalues and eigenvectors of a tridiagonal symmetric matrix
+
+    The algorithm finds the eigen pairs of a tridiagonal symmetric matrix by
+    using an QL/QR algorithm with implicit shifts.
+
+    Input parameters:
+        D       -   the main diagonal of a tridiagonal matrix.
+                    Array whose index ranges within [0..N-1].
+        E       -   the secondary diagonal of a tridiagonal matrix.
+                    Array whose index ranges within [0..N-2].
+        N       -   size of matrix A.
+        ZNeeded -   flag controlling whether the eigenvectors are needed or not.
+                    If ZNeeded is equal to:
+                     * 0, the eigenvectors are not needed;
+                     * 1, the eigenvectors of a tridiagonal matrix
+                       are multiplied by the square matrix Z. It is used if the
+                       tridiagonal matrix is obtained by the similarity
+                       transformation of a symmetric matrix;
+                     * 2, the eigenvectors of a tridiagonal matrix replace the
+                       square matrix Z;
+                     * 3, matrix Z contains the first row of the eigenvectors
+                       matrix.
+        Z       -   if ZNeeded=1, Z contains the square matrix by which the
+                    eigenvectors are multiplied.
+                    Array whose indexes range within [0..N-1, 0..N-1].
+
+    Output parameters:
+        D       -   eigenvalues in ascending order.
+                    Array whose index ranges within [0..N-1].
+        Z       -   if ZNeeded is equal to:
+                     * 0, Z hasnt changed;
+                     * 1, Z contains the product of a given matrix (from the left)
+                       and the eigenvectors matrix (from the right);
+                     * 2, Z contains the eigenvectors.
+                     * 3, Z contains the first row of the eigenvectors matrix.
+                    If ZNeeded<3, Z is the array whose indexes range within [0..N-1, 0..N-1].
+                    In that case, the eigenvectors are stored in the matrix columns.
+                    If ZNeeded=3, Z is the array whose indexes range within [0..0, 0..N-1].
+
+    Result:
+        True, if the algorithm has converged.
+        False, if the algorithm hasn't converged.
+
+      -- LAPACK routine (version 3.0) --
+         Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd.,
+         Courant Institute, Argonne National Lab, and Rice University
+         September 30, 1994
+    *************************************************************************/
+    public static bool smatrixtdevd(ref double[] d, double[] e, int n, int zneeded, ref double[,] z)
+    {
+
+        bool result = evd.smatrixtdevd(ref d, e, n, zneeded, ref z);
+        return result;
+    }
+
+    /*************************************************************************
+    Subroutine for finding the tridiagonal matrix eigenvalues/vectors in a
+    given half-interval (A, B] by using bisection and inverse iteration.
+
+    Input parameters:
+        D       -   the main diagonal of a tridiagonal matrix.
+                    Array whose index ranges within [0..N-1].
+        E       -   the secondary diagonal of a tridiagonal matrix.
+                    Array whose index ranges within [0..N-2].
+        N       -   size of matrix, N>=0.
+        ZNeeded -   flag controlling whether the eigenvectors are needed or not.
+                    If ZNeeded is equal to:
+                     * 0, the eigenvectors are not needed;
+                     * 1, the eigenvectors of a tridiagonal matrix are multiplied
+                       by the square matrix Z. It is used if the tridiagonal
+                       matrix is obtained by the similarity transformation
+                       of a symmetric matrix.
+                     * 2, the eigenvectors of a tridiagonal matrix replace matrix Z.
+        A, B    -   half-interval (A, B] to search eigenvalues in.
+        Z       -   if ZNeeded is equal to:
+                     * 0, Z isn't used and remains unchanged;
+                     * 1, Z contains the square matrix (array whose indexes range
+                       within [0..N-1, 0..N-1]) which reduces the given symmetric
+                       matrix to tridiagonal form;
+                     * 2, Z isn't used (but changed on the exit).
+
+    Output parameters:
+        D       -   array of the eigenvalues found.
+                    Array whose index ranges within [0..M-1].
+        M       -   number of eigenvalues found in the given half-interval (M>=0).
+        Z       -   if ZNeeded is equal to:
+                     * 0, doesn't contain any information;
+                     * 1, contains the product of a given NxN matrix Z (from the
+                       left) and NxM matrix of the eigenvectors found (from the
+                       right). Array whose indexes range within [0..N-1, 0..M-1].
+                     * 2, contains the matrix of the eigenvectors found.
+                       Array whose indexes range within [0..N-1, 0..M-1].
+
+    Result:
+
+        True, if successful. In that case, M contains the number of eigenvalues
+        in the given half-interval (could be equal to 0), D contains the eigenvalues,
+        Z contains the eigenvectors (if needed).
+        It should be noted that the subroutine changes the size of arrays D and Z.
+
+        False, if the bisection method subroutine wasn't able to find the
+        eigenvalues in the given interval or if the inverse iteration subroutine
+        wasn't able to find all the corresponding eigenvectors. In that case,
+        the eigenvalues and eigenvectors are not returned, M is equal to 0.
+
+      -- ALGLIB --
+         Copyright 31.03.2008 by Bochkanov Sergey
+    *************************************************************************/
+    public static bool smatrixtdevdr(ref double[] d, double[] e, int n, int zneeded, double a, double b, out int m, ref double[,] z)
+    {
+        m = 0;
+        bool result = evd.smatrixtdevdr(ref d, e, n, zneeded, a, b, ref m, ref z);
+        return result;
+    }
+
+    /*************************************************************************
+    Subroutine for finding tridiagonal matrix eigenvalues/vectors with given
+    indexes (in ascending order) by using the bisection and inverse iteraion.
+
+    Input parameters:
+        D       -   the main diagonal of a tridiagonal matrix.
+                    Array whose index ranges within [0..N-1].
+        E       -   the secondary diagonal of a tridiagonal matrix.
+                    Array whose index ranges within [0..N-2].
+        N       -   size of matrix. N>=0.
+        ZNeeded -   flag controlling whether the eigenvectors are needed or not.
+                    If ZNeeded is equal to:
+                     * 0, the eigenvectors are not needed;
+                     * 1, the eigenvectors of a tridiagonal matrix are multiplied
+                       by the square matrix Z. It is used if the
+                       tridiagonal matrix is obtained by the similarity transformation
+                       of a symmetric matrix.
+                     * 2, the eigenvectors of a tridiagonal matrix replace
+                       matrix Z.
+        I1, I2  -   index interval for searching (from I1 to I2).
+                    0 <= I1 <= I2 <= N-1.
+        Z       -   if ZNeeded is equal to:
+                     * 0, Z isn't used and remains unchanged;
+                     * 1, Z contains the square matrix (array whose indexes range within [0..N-1, 0..N-1])
+                       which reduces the given symmetric matrix to  tridiagonal form;
+                     * 2, Z isn't used (but changed on the exit).
+
+    Output parameters:
+        D       -   array of the eigenvalues found.
+                    Array whose index ranges within [0..I2-I1].
+        Z       -   if ZNeeded is equal to:
+                     * 0, doesn't contain any information;
+                     * 1, contains the product of a given NxN matrix Z (from the left) and
+                       Nx(I2-I1) matrix of the eigenvectors found (from the right).
+                       Array whose indexes range within [0..N-1, 0..I2-I1].
+                     * 2, contains the matrix of the eigenvalues found.
+                       Array whose indexes range within [0..N-1, 0..I2-I1].
+
+
+    Result:
+
+        True, if successful. In that case, D contains the eigenvalues,
+        Z contains the eigenvectors (if needed).
+        It should be noted that the subroutine changes the size of arrays D and Z.
+
+        False, if the bisection method subroutine wasn't able to find the eigenvalues
+        in the given interval or if the inverse iteration subroutine wasn't able
+        to find all the corresponding eigenvectors. In that case, the eigenvalues
+        and eigenvectors are not returned.
+
+      -- ALGLIB --
+         Copyright 25.12.2005 by Bochkanov Sergey
+    *************************************************************************/
+    public static bool smatrixtdevdi(ref double[] d, double[] e, int n, int zneeded, int i1, int i2, ref double[,] z)
+    {
+
+        bool result = evd.smatrixtdevdi(ref d, e, n, zneeded, i1, i2, ref z);
+        return result;
+    }
+
+    /*************************************************************************
+    Finding eigenvalues and eigenvectors of a general matrix
+
+    The algorithm finds eigenvalues and eigenvectors of a general matrix by
+    using the QR algorithm with multiple shifts. The algorithm can find
+    eigenvalues and both left and right eigenvectors.
+
+    The right eigenvector is a vector x such that A*x = w*x, and the left
+    eigenvector is a vector y such that y'*A = w*y' (here y' implies a complex
+    conjugate transposition of vector y).
+
+    Input parameters:
+        A       -   matrix. Array whose indexes range within [0..N-1, 0..N-1].
+        N       -   size of matrix A.
+        VNeeded -   flag controlling whether eigenvectors are needed or not.
+                    If VNeeded is equal to:
+                     * 0, eigenvectors are not returned;
+                     * 1, right eigenvectors are returned;
+                     * 2, left eigenvectors are returned;
+                     * 3, both left and right eigenvectors are returned.
+
+    Output parameters:
+        WR      -   real parts of eigenvalues.
+                    Array whose index ranges within [0..N-1].
+        WR      -   imaginary parts of eigenvalues.
+                    Array whose index ranges within [0..N-1].
+        VL, VR  -   arrays of left and right eigenvectors (if they are needed).
+                    If WI[i]=0, the respective eigenvalue is a real number,
+                    and it corresponds to the column number I of matrices VL/VR.
+                    If WI[i]>0, we have a pair of complex conjugate numbers with
+                    positive and negative imaginary parts:
+                        the first eigenvalue WR[i] + sqrt(-1)*WI[i];
+                        the second eigenvalue WR[i+1] + sqrt(-1)*WI[i+1];
+                        WI[i]>0
+                        WI[i+1] = -WI[i] < 0
+                    In that case, the eigenvector  corresponding to the first
+                    eigenvalue is located in i and i+1 columns of matrices
+                    VL/VR (the column number i contains the real part, and the
+                    column number i+1 contains the imaginary part), and the vector
+                    corresponding to the second eigenvalue is a complex conjugate to
+                    the first vector.
+                    Arrays whose indexes range within [0..N-1, 0..N-1].
+
+    Result:
+        True, if the algorithm has converged.
+        False, if the algorithm has not converged.
+
+    Note 1:
+        Some users may ask the following question: what if WI[N-1]>0?
+        WI[N] must contain an eigenvalue which is complex conjugate to the
+        N-th eigenvalue, but the array has only size N?
+        The answer is as follows: such a situation cannot occur because the
+        algorithm finds a pairs of eigenvalues, therefore, if WI[i]>0, I is
+        strictly less than N-1.
+
+    Note 2:
+        The algorithm performance depends on the value of the internal parameter
+        NS of the InternalSchurDecomposition subroutine which defines the number
+        of shifts in the QR algorithm (similarly to the block width in block-matrix
+        algorithms of linear algebra). If you require maximum performance
+        on your machine, it is recommended to adjust this parameter manually.
+
+
+    See also the InternalTREVC subroutine.
+
+    The algorithm is based on the LAPACK 3.0 library.
+    *************************************************************************/
+    public static bool rmatrixevd(double[,] a, int n, int vneeded, out double[] wr, out double[] wi, out double[,] vl, out double[,] vr)
+    {
+        wr = new double[0];
+        wi = new double[0];
+        vl = new double[0,0];
+        vr = new double[0,0];
+        bool result = evd.rmatrixevd(a, n, vneeded, ref wr, ref wi, ref vl, ref vr);
+        return result;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    Generation of a random uniformly distributed (Haar) orthogonal matrix
+
+    INPUT PARAMETERS:
+        N   -   matrix size, N>=1
+
+    OUTPUT PARAMETERS:
+        A   -   orthogonal NxN matrix, array[0..N-1,0..N-1]
+
+      -- ALGLIB routine --
+         04.12.2009
+         Bochkanov Sergey
+    *************************************************************************/
+    public static void rmatrixrndorthogonal(int n, out double[,] a)
+    {
+        a = new double[0,0];
+        matgen.rmatrixrndorthogonal(n, ref a);
+        return;
+    }
+
+    /*************************************************************************
+    Generation of random NxN matrix with given condition number and norm2(A)=1
+
+    INPUT PARAMETERS:
+        N   -   matrix size
+        C   -   condition number (in 2-norm)
+
+    OUTPUT PARAMETERS:
+        A   -   random matrix with norm2(A)=1 and cond(A)=C
+
+      -- ALGLIB routine --
+         04.12.2009
+         Bochkanov Sergey
+    *************************************************************************/
+    public static void rmatrixrndcond(int n, double c, out double[,] a)
+    {
+        a = new double[0,0];
+        matgen.rmatrixrndcond(n, c, ref a);
+        return;
+    }
+
+    /*************************************************************************
+    Generation of a random Haar distributed orthogonal complex matrix
+
+    INPUT PARAMETERS:
+        N   -   matrix size, N>=1
+
+    OUTPUT PARAMETERS:
+        A   -   orthogonal NxN matrix, array[0..N-1,0..N-1]
+
+      -- ALGLIB routine --
+         04.12.2009
+         Bochkanov Sergey
+    *************************************************************************/
+    public static void cmatrixrndorthogonal(int n, out complex[,] a)
+    {
+        a = new complex[0,0];
+        matgen.cmatrixrndorthogonal(n, ref a);
+        return;
+    }
+
+    /*************************************************************************
+    Generation of random NxN complex matrix with given condition number C and
+    norm2(A)=1
+
+    INPUT PARAMETERS:
+        N   -   matrix size
+        C   -   condition number (in 2-norm)
+
+    OUTPUT PARAMETERS:
+        A   -   random matrix with norm2(A)=1 and cond(A)=C
+
+      -- ALGLIB routine --
+         04.12.2009
+         Bochkanov Sergey
+    *************************************************************************/
+    public static void cmatrixrndcond(int n, double c, out complex[,] a)
+    {
+        a = new complex[0,0];
+        matgen.cmatrixrndcond(n, c, ref a);
+        return;
+    }
+
+    /*************************************************************************
+    Generation of random NxN symmetric matrix with given condition number  and
+    norm2(A)=1
+
+    INPUT PARAMETERS:
+        N   -   matrix size
+        C   -   condition number (in 2-norm)
+
+    OUTPUT PARAMETERS:
+        A   -   random matrix with norm2(A)=1 and cond(A)=C
+
+      -- ALGLIB routine --
+         04.12.2009
+         Bochkanov Sergey
+    *************************************************************************/
+    public static void smatrixrndcond(int n, double c, out double[,] a)
+    {
+        a = new double[0,0];
+        matgen.smatrixrndcond(n, c, ref a);
+        return;
+    }
+
+    /*************************************************************************
+    Generation of random NxN symmetric positive definite matrix with given
+    condition number and norm2(A)=1
+
+    INPUT PARAMETERS:
+        N   -   matrix size
+        C   -   condition number (in 2-norm)
+
+    OUTPUT PARAMETERS:
+        A   -   random SPD matrix with norm2(A)=1 and cond(A)=C
+
+      -- ALGLIB routine --
+         04.12.2009
+         Bochkanov Sergey
+    *************************************************************************/
+    public static void spdmatrixrndcond(int n, double c, out double[,] a)
+    {
+        a = new double[0,0];
+        matgen.spdmatrixrndcond(n, c, ref a);
+        return;
+    }
+
+    /*************************************************************************
+    Generation of random NxN Hermitian matrix with given condition number  and
+    norm2(A)=1
+
+    INPUT PARAMETERS:
+        N   -   matrix size
+        C   -   condition number (in 2-norm)
+
+    OUTPUT PARAMETERS:
+        A   -   random matrix with norm2(A)=1 and cond(A)=C
+
+      -- ALGLIB routine --
+         04.12.2009
+         Bochkanov Sergey
+    *************************************************************************/
+    public static void hmatrixrndcond(int n, double c, out complex[,] a)
+    {
+        a = new complex[0,0];
+        matgen.hmatrixrndcond(n, c, ref a);
+        return;
+    }
+
+    /*************************************************************************
+    Generation of random NxN Hermitian positive definite matrix with given
+    condition number and norm2(A)=1
+
+    INPUT PARAMETERS:
+        N   -   matrix size
+        C   -   condition number (in 2-norm)
+
+    OUTPUT PARAMETERS:
+        A   -   random HPD matrix with norm2(A)=1 and cond(A)=C
+
+      -- ALGLIB routine --
+         04.12.2009
+         Bochkanov Sergey
+    *************************************************************************/
+    public static void hpdmatrixrndcond(int n, double c, out complex[,] a)
+    {
+        a = new complex[0,0];
+        matgen.hpdmatrixrndcond(n, c, ref a);
+        return;
+    }
+
+    /*************************************************************************
+    Multiplication of MxN matrix by NxN random Haar distributed orthogonal matrix
+
+    INPUT PARAMETERS:
+        A   -   matrix, array[0..M-1, 0..N-1]
+        M, N-   matrix size
+
+    OUTPUT PARAMETERS:
+        A   -   A*Q, where Q is random NxN orthogonal matrix
+
+      -- ALGLIB routine --
+         04.12.2009
+         Bochkanov Sergey
+    *************************************************************************/
+    public static void rmatrixrndorthogonalfromtheright(ref double[,] a, int m, int n)
+    {
+
+        matgen.rmatrixrndorthogonalfromtheright(ref a, m, n);
+        return;
+    }
+
+    /*************************************************************************
+    Multiplication of MxN matrix by MxM random Haar distributed orthogonal matrix
+
+    INPUT PARAMETERS:
+        A   -   matrix, array[0..M-1, 0..N-1]
+        M, N-   matrix size
+
+    OUTPUT PARAMETERS:
+        A   -   Q*A, where Q is random MxM orthogonal matrix
+
+      -- ALGLIB routine --
+         04.12.2009
+         Bochkanov Sergey
+    *************************************************************************/
+    public static void rmatrixrndorthogonalfromtheleft(ref double[,] a, int m, int n)
+    {
+
+        matgen.rmatrixrndorthogonalfromtheleft(ref a, m, n);
+        return;
+    }
+
+    /*************************************************************************
+    Multiplication of MxN complex matrix by NxN random Haar distributed
+    complex orthogonal matrix
+
+    INPUT PARAMETERS:
+        A   -   matrix, array[0..M-1, 0..N-1]
+        M, N-   matrix size
+
+    OUTPUT PARAMETERS:
+        A   -   A*Q, where Q is random NxN orthogonal matrix
+
+      -- ALGLIB routine --
+         04.12.2009
+         Bochkanov Sergey
+    *************************************************************************/
+    public static void cmatrixrndorthogonalfromtheright(ref complex[,] a, int m, int n)
+    {
+
+        matgen.cmatrixrndorthogonalfromtheright(ref a, m, n);
+        return;
+    }
+
+    /*************************************************************************
+    Multiplication of MxN complex matrix by MxM random Haar distributed
+    complex orthogonal matrix
+
+    INPUT PARAMETERS:
+        A   -   matrix, array[0..M-1, 0..N-1]
+        M, N-   matrix size
+
+    OUTPUT PARAMETERS:
+        A   -   Q*A, where Q is random MxM orthogonal matrix
+
+      -- ALGLIB routine --
+         04.12.2009
+         Bochkanov Sergey
+    *************************************************************************/
+    public static void cmatrixrndorthogonalfromtheleft(ref complex[,] a, int m, int n)
+    {
+
+        matgen.cmatrixrndorthogonalfromtheleft(ref a, m, n);
+        return;
+    }
+
+    /*************************************************************************
+    Symmetric multiplication of NxN matrix by random Haar distributed
+    orthogonal  matrix
+
+    INPUT PARAMETERS:
+        A   -   matrix, array[0..N-1, 0..N-1]
+        N   -   matrix size
+
+    OUTPUT PARAMETERS:
+        A   -   Q'*A*Q, where Q is random NxN orthogonal matrix
+
+      -- ALGLIB routine --
+         04.12.2009
+         Bochkanov Sergey
+    *************************************************************************/
+    public static void smatrixrndmultiply(ref double[,] a, int n)
+    {
+
+        matgen.smatrixrndmultiply(ref a, n);
+        return;
+    }
+
+    /*************************************************************************
+    Hermitian multiplication of NxN matrix by random Haar distributed
+    complex orthogonal matrix
+
+    INPUT PARAMETERS:
+        A   -   matrix, array[0..N-1, 0..N-1]
+        N   -   matrix size
+
+    OUTPUT PARAMETERS:
+        A   -   Q^H*A*Q, where Q is random NxN orthogonal matrix
+
+      -- ALGLIB routine --
+         04.12.2009
+         Bochkanov Sergey
+    *************************************************************************/
+    public static void hmatrixrndmultiply(ref complex[,] a, int n)
+    {
+
+        matgen.hmatrixrndmultiply(ref a, n);
+        return;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    LU decomposition of a general real matrix with row pivoting
+
+    A is represented as A = P*L*U, where:
+    * L is lower unitriangular matrix
+    * U is upper triangular matrix
+    * P = P0*P1*...*PK, K=min(M,N)-1,
+      Pi - permutation matrix for I and Pivots[I]
+
+    This is cache-oblivous implementation of LU decomposition.
+    It is optimized for square matrices. As for rectangular matrices:
+    * best case - M>>N
+    * worst case - N>>M, small M, large N, matrix does not fit in CPU cache
+
+    INPUT PARAMETERS:
+        A       -   array[0..M-1, 0..N-1].
+        M       -   number of rows in matrix A.
+        N       -   number of columns in matrix A.
+
+
+    OUTPUT PARAMETERS:
+        A       -   matrices L and U in compact form:
+                    * L is stored under main diagonal
+                    * U is stored on and above main diagonal
+        Pivots  -   permutation matrix in compact form.
+                    array[0..Min(M-1,N-1)].
+
+      -- ALGLIB routine --
+         10.01.2010
+         Bochkanov Sergey
+    *************************************************************************/
+    public static void rmatrixlu(ref double[,] a, int m, int n, out int[] pivots)
+    {
+        pivots = new int[0];
+        trfac.rmatrixlu(ref a, m, n, ref pivots);
+        return;
+    }
+
+    /*************************************************************************
+    LU decomposition of a general complex matrix with row pivoting
+
+    A is represented as A = P*L*U, where:
+    * L is lower unitriangular matrix
+    * U is upper triangular matrix
+    * P = P0*P1*...*PK, K=min(M,N)-1,
+      Pi - permutation matrix for I and Pivots[I]
+
+    This is cache-oblivous implementation of LU decomposition. It is optimized
+    for square matrices. As for rectangular matrices:
+    * best case - M>>N
+    * worst case - N>>M, small M, large N, matrix does not fit in CPU cache
+
+    INPUT PARAMETERS:
+        A       -   array[0..M-1, 0..N-1].
+        M       -   number of rows in matrix A.
+        N       -   number of columns in matrix A.
+
+
+    OUTPUT PARAMETERS:
+        A       -   matrices L and U in compact form:
+                    * L is stored under main diagonal
+                    * U is stored on and above main diagonal
+        Pivots  -   permutation matrix in compact form.
+                    array[0..Min(M-1,N-1)].
+
+      -- ALGLIB routine --
+         10.01.2010
+         Bochkanov Sergey
+    *************************************************************************/
+    public static void cmatrixlu(ref complex[,] a, int m, int n, out int[] pivots)
+    {
+        pivots = new int[0];
+        trfac.cmatrixlu(ref a, m, n, ref pivots);
+        return;
+    }
+
+    /*************************************************************************
+    Cache-oblivious Cholesky decomposition
+
+    The algorithm computes Cholesky decomposition  of  a  Hermitian  positive-
+    definite matrix. The result of an algorithm is a representation  of  A  as
+    A=U'*U  or A=L*L' (here X' detones conj(X^T)).
+
+    INPUT PARAMETERS:
+        A       -   upper or lower triangle of a factorized matrix.
+                    array with elements [0..N-1, 0..N-1].
+        N       -   size of matrix A.
+        IsUpper -   if IsUpper=True, then A contains an upper triangle of
+                    a symmetric matrix, otherwise A contains a lower one.
+
+    OUTPUT PARAMETERS:
+        A       -   the result of factorization. If IsUpper=True, then
+                    the upper triangle contains matrix U, so that A = U'*U,
+                    and the elements below the main diagonal are not modified.
+                    Similarly, if IsUpper = False.
+
+    RESULT:
+        If  the  matrix  is  positive-definite,  the  function  returns  True.
+        Otherwise, the function returns False. Contents of A is not determined
+        in such case.
+
+      -- ALGLIB routine --
+         15.12.2009
+         Bochkanov Sergey
+    *************************************************************************/
+    public static bool hpdmatrixcholesky(ref complex[,] a, int n, bool isupper)
+    {
+
+        bool result = trfac.hpdmatrixcholesky(ref a, n, isupper);
+        return result;
+    }
+
+    /*************************************************************************
+    Cache-oblivious Cholesky decomposition
+
+    The algorithm computes Cholesky decomposition  of  a  symmetric  positive-
+    definite matrix. The result of an algorithm is a representation  of  A  as
+    A=U^T*U  or A=L*L^T
+
+    INPUT PARAMETERS:
+        A       -   upper or lower triangle of a factorized matrix.
+                    array with elements [0..N-1, 0..N-1].
+        N       -   size of matrix A.
+        IsUpper -   if IsUpper=True, then A contains an upper triangle of
+                    a symmetric matrix, otherwise A contains a lower one.
+
+    OUTPUT PARAMETERS:
+        A       -   the result of factorization. If IsUpper=True, then
+                    the upper triangle contains matrix U, so that A = U^T*U,
+                    and the elements below the main diagonal are not modified.
+                    Similarly, if IsUpper = False.
+
+    RESULT:
+        If  the  matrix  is  positive-definite,  the  function  returns  True.
+        Otherwise, the function returns False. Contents of A is not determined
+        in such case.
+
+      -- ALGLIB routine --
+         15.12.2009
+         Bochkanov Sergey
+    *************************************************************************/
+    public static bool spdmatrixcholesky(ref double[,] a, int n, bool isupper)
+    {
+
+        bool result = trfac.spdmatrixcholesky(ref a, n, isupper);
+        return result;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    Estimate of a matrix condition number (1-norm)
+
+    The algorithm calculates a lower bound of the condition number. In this case,
+    the algorithm does not return a lower bound of the condition number, but an
+    inverse number (to avoid an overflow in case of a singular matrix).
+
+    Input parameters:
+        A   -   matrix. Array whose indexes range within [0..N-1, 0..N-1].
+        N   -   size of matrix A.
+
+    Result: 1/LowerBound(cond(A))
+
+    NOTE:
+        if k(A) is very large, then matrix is  assumed  degenerate,  k(A)=INF,
+        0.0 is returned in such cases.
+    *************************************************************************/
+    public static double rmatrixrcond1(double[,] a, int n)
+    {
+
+        double result = rcond.rmatrixrcond1(a, n);
+        return result;
+    }
+
+    /*************************************************************************
+    Estimate of a matrix condition number (infinity-norm).
+
+    The algorithm calculates a lower bound of the condition number. In this case,
+    the algorithm does not return a lower bound of the condition number, but an
+    inverse number (to avoid an overflow in case of a singular matrix).
+
+    Input parameters:
+        A   -   matrix. Array whose indexes range within [0..N-1, 0..N-1].
+        N   -   size of matrix A.
+
+    Result: 1/LowerBound(cond(A))
+
+    NOTE:
+        if k(A) is very large, then matrix is  assumed  degenerate,  k(A)=INF,
+        0.0 is returned in such cases.
+    *************************************************************************/
+    public static double rmatrixrcondinf(double[,] a, int n)
+    {
+
+        double result = rcond.rmatrixrcondinf(a, n);
+        return result;
+    }
+
+    /*************************************************************************
+    Condition number estimate of a symmetric positive definite matrix.
+
+    The algorithm calculates a lower bound of the condition number. In this case,
+    the algorithm does not return a lower bound of the condition number, but an
+    inverse number (to avoid an overflow in case of a singular matrix).
+
+    It should be noted that 1-norm and inf-norm of condition numbers of symmetric
+    matrices are equal, so the algorithm doesn't take into account the
+    differences between these types of norms.
+
+    Input parameters:
+        A       -   symmetric positive definite matrix which is given by its
+                    upper or lower triangle depending on the value of
+                    IsUpper. Array with elements [0..N-1, 0..N-1].
+        N       -   size of matrix A.
+        IsUpper -   storage format.
+
+    Result:
+        1/LowerBound(cond(A)), if matrix A is positive definite,
+       -1, if matrix A is not positive definite, and its condition number
+        could not be found by this algorithm.
+
+    NOTE:
+        if k(A) is very large, then matrix is  assumed  degenerate,  k(A)=INF,
+        0.0 is returned in such cases.
+    *************************************************************************/
+    public static double spdmatrixrcond(double[,] a, int n, bool isupper)
+    {
+
+        double result = rcond.spdmatrixrcond(a, n, isupper);
+        return result;
+    }
+
+    /*************************************************************************
+    Triangular matrix: estimate of a condition number (1-norm)
+
+    The algorithm calculates a lower bound of the condition number. In this case,
+    the algorithm does not return a lower bound of the condition number, but an
+    inverse number (to avoid an overflow in case of a singular matrix).
+
+    Input parameters:
+        A       -   matrix. Array[0..N-1, 0..N-1].
+        N       -   size of A.
+        IsUpper -   True, if the matrix is upper triangular.
+        IsUnit  -   True, if the matrix has a unit diagonal.
+
+    Result: 1/LowerBound(cond(A))
+
+    NOTE:
+        if k(A) is very large, then matrix is  assumed  degenerate,  k(A)=INF,
+        0.0 is returned in such cases.
+    *************************************************************************/
+    public static double rmatrixtrrcond1(double[,] a, int n, bool isupper, bool isunit)
+    {
+
+        double result = rcond.rmatrixtrrcond1(a, n, isupper, isunit);
+        return result;
+    }
+
+    /*************************************************************************
+    Triangular matrix: estimate of a matrix condition number (infinity-norm).
+
+    The algorithm calculates a lower bound of the condition number. In this case,
+    the algorithm does not return a lower bound of the condition number, but an
+    inverse number (to avoid an overflow in case of a singular matrix).
+
+    Input parameters:
+        A   -   matrix. Array whose indexes range within [0..N-1, 0..N-1].
+        N   -   size of matrix A.
+        IsUpper -   True, if the matrix is upper triangular.
+        IsUnit  -   True, if the matrix has a unit diagonal.
+
+    Result: 1/LowerBound(cond(A))
+
+    NOTE:
+        if k(A) is very large, then matrix is  assumed  degenerate,  k(A)=INF,
+        0.0 is returned in such cases.
+    *************************************************************************/
+    public static double rmatrixtrrcondinf(double[,] a, int n, bool isupper, bool isunit)
+    {
+
+        double result = rcond.rmatrixtrrcondinf(a, n, isupper, isunit);
+        return result;
+    }
+
+    /*************************************************************************
+    Condition number estimate of a Hermitian positive definite matrix.
+
+    The algorithm calculates a lower bound of the condition number. In this case,
+    the algorithm does not return a lower bound of the condition number, but an
+    inverse number (to avoid an overflow in case of a singular matrix).
+
+    It should be noted that 1-norm and inf-norm of condition numbers of symmetric
+    matrices are equal, so the algorithm doesn't take into account the
+    differences between these types of norms.
+
+    Input parameters:
+        A       -   Hermitian positive definite matrix which is given by its
+                    upper or lower triangle depending on the value of
+                    IsUpper. Array with elements [0..N-1, 0..N-1].
+        N       -   size of matrix A.
+        IsUpper -   storage format.
+
+    Result:
+        1/LowerBound(cond(A)), if matrix A is positive definite,
+       -1, if matrix A is not positive definite, and its condition number
+        could not be found by this algorithm.
+
+    NOTE:
+        if k(A) is very large, then matrix is  assumed  degenerate,  k(A)=INF,
+        0.0 is returned in such cases.
+    *************************************************************************/
+    public static double hpdmatrixrcond(complex[,] a, int n, bool isupper)
+    {
+
+        double result = rcond.hpdmatrixrcond(a, n, isupper);
+        return result;
+    }
+
+    /*************************************************************************
+    Estimate of a matrix condition number (1-norm)
+
+    The algorithm calculates a lower bound of the condition number. In this case,
+    the algorithm does not return a lower bound of the condition number, but an
+    inverse number (to avoid an overflow in case of a singular matrix).
+
+    Input parameters:
+        A   -   matrix. Array whose indexes range within [0..N-1, 0..N-1].
+        N   -   size of matrix A.
+
+    Result: 1/LowerBound(cond(A))
+
+    NOTE:
+        if k(A) is very large, then matrix is  assumed  degenerate,  k(A)=INF,
+        0.0 is returned in such cases.
+    *************************************************************************/
+    public static double cmatrixrcond1(complex[,] a, int n)
+    {
+
+        double result = rcond.cmatrixrcond1(a, n);
+        return result;
+    }
+
+    /*************************************************************************
+    Estimate of a matrix condition number (infinity-norm).
+
+    The algorithm calculates a lower bound of the condition number. In this case,
+    the algorithm does not return a lower bound of the condition number, but an
+    inverse number (to avoid an overflow in case of a singular matrix).
+
+    Input parameters:
+        A   -   matrix. Array whose indexes range within [0..N-1, 0..N-1].
+        N   -   size of matrix A.
+
+    Result: 1/LowerBound(cond(A))
+
+    NOTE:
+        if k(A) is very large, then matrix is  assumed  degenerate,  k(A)=INF,
+        0.0 is returned in such cases.
+    *************************************************************************/
+    public static double cmatrixrcondinf(complex[,] a, int n)
+    {
+
+        double result = rcond.cmatrixrcondinf(a, n);
+        return result;
+    }
+
+    /*************************************************************************
+    Estimate of the condition number of a matrix given by its LU decomposition (1-norm)
+
+    The algorithm calculates a lower bound of the condition number. In this case,
+    the algorithm does not return a lower bound of the condition number, but an
+    inverse number (to avoid an overflow in case of a singular matrix).
+
+    Input parameters:
+        LUA         -   LU decomposition of a matrix in compact form. Output of
+                        the RMatrixLU subroutine.
+        N           -   size of matrix A.
+
+    Result: 1/LowerBound(cond(A))
+
+    NOTE:
+        if k(A) is very large, then matrix is  assumed  degenerate,  k(A)=INF,
+        0.0 is returned in such cases.
+    *************************************************************************/
+    public static double rmatrixlurcond1(double[,] lua, int n)
+    {
+
+        double result = rcond.rmatrixlurcond1(lua, n);
+        return result;
+    }
+
+    /*************************************************************************
+    Estimate of the condition number of a matrix given by its LU decomposition
+    (infinity norm).
+
+    The algorithm calculates a lower bound of the condition number. In this case,
+    the algorithm does not return a lower bound of the condition number, but an
+    inverse number (to avoid an overflow in case of a singular matrix).
+
+    Input parameters:
+        LUA     -   LU decomposition of a matrix in compact form. Output of
+                    the RMatrixLU subroutine.
+        N       -   size of matrix A.
+
+    Result: 1/LowerBound(cond(A))
+
+    NOTE:
+        if k(A) is very large, then matrix is  assumed  degenerate,  k(A)=INF,
+        0.0 is returned in such cases.
+    *************************************************************************/
+    public static double rmatrixlurcondinf(double[,] lua, int n)
+    {
+
+        double result = rcond.rmatrixlurcondinf(lua, n);
+        return result;
+    }
+
+    /*************************************************************************
+    Condition number estimate of a symmetric positive definite matrix given by
+    Cholesky decomposition.
+
+    The algorithm calculates a lower bound of the condition number. In this
+    case, the algorithm does not return a lower bound of the condition number,
+    but an inverse number (to avoid an overflow in case of a singular matrix).
+
+    It should be noted that 1-norm and inf-norm condition numbers of symmetric
+    matrices are equal, so the algorithm doesn't take into account the
+    differences between these types of norms.
+
+    Input parameters:
+        CD  - Cholesky decomposition of matrix A,
+              output of SMatrixCholesky subroutine.
+        N   - size of matrix A.
+
+    Result: 1/LowerBound(cond(A))
+
+    NOTE:
+        if k(A) is very large, then matrix is  assumed  degenerate,  k(A)=INF,
+        0.0 is returned in such cases.
+    *************************************************************************/
+    public static double spdmatrixcholeskyrcond(double[,] a, int n, bool isupper)
+    {
+
+        double result = rcond.spdmatrixcholeskyrcond(a, n, isupper);
+        return result;
+    }
+
+    /*************************************************************************
+    Condition number estimate of a Hermitian positive definite matrix given by
+    Cholesky decomposition.
+
+    The algorithm calculates a lower bound of the condition number. In this
+    case, the algorithm does not return a lower bound of the condition number,
+    but an inverse number (to avoid an overflow in case of a singular matrix).
+
+    It should be noted that 1-norm and inf-norm condition numbers of symmetric
+    matrices are equal, so the algorithm doesn't take into account the
+    differences between these types of norms.
+
+    Input parameters:
+        CD  - Cholesky decomposition of matrix A,
+              output of SMatrixCholesky subroutine.
+        N   - size of matrix A.
+
+    Result: 1/LowerBound(cond(A))
+
+    NOTE:
+        if k(A) is very large, then matrix is  assumed  degenerate,  k(A)=INF,
+        0.0 is returned in such cases.
+    *************************************************************************/
+    public static double hpdmatrixcholeskyrcond(complex[,] a, int n, bool isupper)
+    {
+
+        double result = rcond.hpdmatrixcholeskyrcond(a, n, isupper);
+        return result;
+    }
+
+    /*************************************************************************
+    Estimate of the condition number of a matrix given by its LU decomposition (1-norm)
+
+    The algorithm calculates a lower bound of the condition number. In this case,
+    the algorithm does not return a lower bound of the condition number, but an
+    inverse number (to avoid an overflow in case of a singular matrix).
+
+    Input parameters:
+        LUA         -   LU decomposition of a matrix in compact form. Output of
+                        the CMatrixLU subroutine.
+        N           -   size of matrix A.
+
+    Result: 1/LowerBound(cond(A))
+
+    NOTE:
+        if k(A) is very large, then matrix is  assumed  degenerate,  k(A)=INF,
+        0.0 is returned in such cases.
+    *************************************************************************/
+    public static double cmatrixlurcond1(complex[,] lua, int n)
+    {
+
+        double result = rcond.cmatrixlurcond1(lua, n);
+        return result;
+    }
+
+    /*************************************************************************
+    Estimate of the condition number of a matrix given by its LU decomposition
+    (infinity norm).
+
+    The algorithm calculates a lower bound of the condition number. In this case,
+    the algorithm does not return a lower bound of the condition number, but an
+    inverse number (to avoid an overflow in case of a singular matrix).
+
+    Input parameters:
+        LUA     -   LU decomposition of a matrix in compact form. Output of
+                    the CMatrixLU subroutine.
+        N       -   size of matrix A.
+
+    Result: 1/LowerBound(cond(A))
+
+    NOTE:
+        if k(A) is very large, then matrix is  assumed  degenerate,  k(A)=INF,
+        0.0 is returned in such cases.
+    *************************************************************************/
+    public static double cmatrixlurcondinf(complex[,] lua, int n)
+    {
+
+        double result = rcond.cmatrixlurcondinf(lua, n);
+        return result;
+    }
+
+    /*************************************************************************
+    Triangular matrix: estimate of a condition number (1-norm)
+
+    The algorithm calculates a lower bound of the condition number. In this case,
+    the algorithm does not return a lower bound of the condition number, but an
+    inverse number (to avoid an overflow in case of a singular matrix).
+
+    Input parameters:
+        A       -   matrix. Array[0..N-1, 0..N-1].
+        N       -   size of A.
+        IsUpper -   True, if the matrix is upper triangular.
+        IsUnit  -   True, if the matrix has a unit diagonal.
+
+    Result: 1/LowerBound(cond(A))
+
+    NOTE:
+        if k(A) is very large, then matrix is  assumed  degenerate,  k(A)=INF,
+        0.0 is returned in such cases.
+    *************************************************************************/
+    public static double cmatrixtrrcond1(complex[,] a, int n, bool isupper, bool isunit)
+    {
+
+        double result = rcond.cmatrixtrrcond1(a, n, isupper, isunit);
+        return result;
+    }
+
+    /*************************************************************************
+    Triangular matrix: estimate of a matrix condition number (infinity-norm).
+
+    The algorithm calculates a lower bound of the condition number. In this case,
+    the algorithm does not return a lower bound of the condition number, but an
+    inverse number (to avoid an overflow in case of a singular matrix).
+
+    Input parameters:
+        A   -   matrix. Array whose indexes range within [0..N-1, 0..N-1].
+        N   -   size of matrix A.
+        IsUpper -   True, if the matrix is upper triangular.
+        IsUnit  -   True, if the matrix has a unit diagonal.
+
+    Result: 1/LowerBound(cond(A))
+
+    NOTE:
+        if k(A) is very large, then matrix is  assumed  degenerate,  k(A)=INF,
+        0.0 is returned in such cases.
+    *************************************************************************/
+    public static double cmatrixtrrcondinf(complex[,] a, int n, bool isupper, bool isunit)
+    {
+
+        double result = rcond.cmatrixtrrcondinf(a, n, isupper, isunit);
+        return result;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    Matrix inverse report:
+    * R1    reciprocal of condition number in 1-norm
+    * RInf  reciprocal of condition number in inf-norm
+    *************************************************************************/
+    public class matinvreport
+    {
+        //
+        // Public declarations
+        //
+        public double r1 { get { return _innerobj.r1; } set { _innerobj.r1 = value; } }
+        public double rinf { get { return _innerobj.rinf; } set { _innerobj.rinf = value; } }
+
+        public matinvreport()
+        {
+            _innerobj = new matinv.matinvreport();
+        }
+
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private matinv.matinvreport _innerobj;
+        public matinv.matinvreport innerobj { get { return _innerobj; } }
+        public matinvreport(matinv.matinvreport obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+    /*************************************************************************
+    Inversion of a matrix given by its LU decomposition.
+
+    INPUT PARAMETERS:
+        A       -   LU decomposition of the matrix
+                    (output of RMatrixLU subroutine).
+        Pivots  -   table of permutations
+                    (the output of RMatrixLU subroutine).
+        N       -   size of matrix A (optional) :
+                    * if given, only principal NxN submatrix is processed  and
+                      overwritten. other elements are unchanged.
+                    * if not given,  size  is  automatically  determined  from
+                      matrix size (A must be square matrix)
+
+    OUTPUT PARAMETERS:
+        Info    -   return code:
+                    * -3    A is singular, or VERY close to singular.
+                            it is filled by zeros in such cases.
+                    *  1    task is solved (but matrix A may be ill-conditioned,
+                            check R1/RInf parameters for condition numbers).
+        Rep     -   solver report, see below for more info
+        A       -   inverse of matrix A.
+                    Array whose indexes range within [0..N-1, 0..N-1].
+
+    SOLVER REPORT
+
+    Subroutine sets following fields of the Rep structure:
+    * R1        reciprocal of condition number: 1/cond(A), 1-norm.
+    * RInf      reciprocal of condition number: 1/cond(A), inf-norm.
+
+      -- ALGLIB routine --
+         05.02.2010
+         Bochkanov Sergey
+    *************************************************************************/
+    public static void rmatrixluinverse(ref double[,] a, int[] pivots, int n, out int info, out matinvreport rep)
+    {
+        info = 0;
+        rep = new matinvreport();
+        matinv.rmatrixluinverse(ref a, pivots, n, ref info, rep.innerobj);
+        return;
+    }
+    public static void rmatrixluinverse(ref double[,] a, int[] pivots, out int info, out matinvreport rep)
+    {
+        int n;
+        if( (ap.cols(a)!=ap.rows(a)) || (ap.cols(a)!=ap.len(pivots)))
+            throw new alglibexception("Error while calling 'rmatrixluinverse': looks like one of arguments has wrong size");
+        info = 0;
+        rep = new matinvreport();
+        n = ap.cols(a);
+        matinv.rmatrixluinverse(ref a, pivots, n, ref info, rep.innerobj);
+
+        return;
+    }
+
+    /*************************************************************************
+    Inversion of a general matrix.
+
+    Input parameters:
+        A       -   matrix.
+        N       -   size of matrix A (optional) :
+                    * if given, only principal NxN submatrix is processed  and
+                      overwritten. other elements are unchanged.
+                    * if not given,  size  is  automatically  determined  from
+                      matrix size (A must be square matrix)
+
+    Output parameters:
+        Info    -   return code, same as in RMatrixLUInverse
+        Rep     -   solver report, same as in RMatrixLUInverse
+        A       -   inverse of matrix A, same as in RMatrixLUInverse
+
+    Result:
+        True, if the matrix is not singular.
+        False, if the matrix is singular.
+
+      -- ALGLIB --
+         Copyright 2005-2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void rmatrixinverse(ref double[,] a, int n, out int info, out matinvreport rep)
+    {
+        info = 0;
+        rep = new matinvreport();
+        matinv.rmatrixinverse(ref a, n, ref info, rep.innerobj);
+        return;
+    }
+    public static void rmatrixinverse(ref double[,] a, out int info, out matinvreport rep)
+    {
+        int n;
+        if( (ap.cols(a)!=ap.rows(a)))
+            throw new alglibexception("Error while calling 'rmatrixinverse': looks like one of arguments has wrong size");
+        info = 0;
+        rep = new matinvreport();
+        n = ap.cols(a);
+        matinv.rmatrixinverse(ref a, n, ref info, rep.innerobj);
+
+        return;
+    }
+
+    /*************************************************************************
+    Inversion of a matrix given by its LU decomposition.
+
+    INPUT PARAMETERS:
+        A       -   LU decomposition of the matrix
+                    (output of CMatrixLU subroutine).
+        Pivots  -   table of permutations
+                    (the output of CMatrixLU subroutine).
+        N       -   size of matrix A (optional) :
+                    * if given, only principal NxN submatrix is processed  and
+                      overwritten. other elements are unchanged.
+                    * if not given,  size  is  automatically  determined  from
+                      matrix size (A must be square matrix)
+
+    OUTPUT PARAMETERS:
+        Info    -   return code, same as in RMatrixLUInverse
+        Rep     -   solver report, same as in RMatrixLUInverse
+        A       -   inverse of matrix A, same as in RMatrixLUInverse
+
+      -- ALGLIB routine --
+         05.02.2010
+         Bochkanov Sergey
+    *************************************************************************/
+    public static void cmatrixluinverse(ref complex[,] a, int[] pivots, int n, out int info, out matinvreport rep)
+    {
+        info = 0;
+        rep = new matinvreport();
+        matinv.cmatrixluinverse(ref a, pivots, n, ref info, rep.innerobj);
+        return;
+    }
+    public static void cmatrixluinverse(ref complex[,] a, int[] pivots, out int info, out matinvreport rep)
+    {
+        int n;
+        if( (ap.cols(a)!=ap.rows(a)) || (ap.cols(a)!=ap.len(pivots)))
+            throw new alglibexception("Error while calling 'cmatrixluinverse': looks like one of arguments has wrong size");
+        info = 0;
+        rep = new matinvreport();
+        n = ap.cols(a);
+        matinv.cmatrixluinverse(ref a, pivots, n, ref info, rep.innerobj);
+
+        return;
+    }
+
+    /*************************************************************************
+    Inversion of a general matrix.
+
+    Input parameters:
+        A       -   matrix
+        N       -   size of matrix A (optional) :
+                    * if given, only principal NxN submatrix is processed  and
+                      overwritten. other elements are unchanged.
+                    * if not given,  size  is  automatically  determined  from
+                      matrix size (A must be square matrix)
+
+    Output parameters:
+        Info    -   return code, same as in RMatrixLUInverse
+        Rep     -   solver report, same as in RMatrixLUInverse
+        A       -   inverse of matrix A, same as in RMatrixLUInverse
+
+      -- ALGLIB --
+         Copyright 2005 by Bochkanov Sergey
+    *************************************************************************/
+    public static void cmatrixinverse(ref complex[,] a, int n, out int info, out matinvreport rep)
+    {
+        info = 0;
+        rep = new matinvreport();
+        matinv.cmatrixinverse(ref a, n, ref info, rep.innerobj);
+        return;
+    }
+    public static void cmatrixinverse(ref complex[,] a, out int info, out matinvreport rep)
+    {
+        int n;
+        if( (ap.cols(a)!=ap.rows(a)))
+            throw new alglibexception("Error while calling 'cmatrixinverse': looks like one of arguments has wrong size");
+        info = 0;
+        rep = new matinvreport();
+        n = ap.cols(a);
+        matinv.cmatrixinverse(ref a, n, ref info, rep.innerobj);
+
+        return;
+    }
+
+    /*************************************************************************
+    Inversion of a symmetric positive definite matrix which is given
+    by Cholesky decomposition.
+
+    Input parameters:
+        A       -   Cholesky decomposition of the matrix to be inverted:
+                    A=U*U or A = L*L'.
+                    Output of  SPDMatrixCholesky subroutine.
+        N       -   size of matrix A (optional) :
+                    * if given, only principal NxN submatrix is processed  and
+                      overwritten. other elements are unchanged.
+                    * if not given,  size  is  automatically  determined  from
+                      matrix size (A must be square matrix)
+        IsUpper -   storage type (optional):
+                    * if True, symmetric  matrix  A  is  given  by  its  upper
+                      triangle, and the lower triangle isnt  used/changed  by
+                      function
+                    * if False,  symmetric matrix  A  is  given  by  its lower
+                      triangle, and the  upper triangle isnt used/changed  by
+                      function
+                    * if not given, lower half is used.
+
+    Output parameters:
+        Info    -   return code, same as in RMatrixLUInverse
+        Rep     -   solver report, same as in RMatrixLUInverse
+        A       -   inverse of matrix A, same as in RMatrixLUInverse
+
+      -- ALGLIB routine --
+         10.02.2010
+         Bochkanov Sergey
+    *************************************************************************/
+    public static void spdmatrixcholeskyinverse(ref double[,] a, int n, bool isupper, out int info, out matinvreport rep)
+    {
+        info = 0;
+        rep = new matinvreport();
+        matinv.spdmatrixcholeskyinverse(ref a, n, isupper, ref info, rep.innerobj);
+        return;
+    }
+    public static void spdmatrixcholeskyinverse(ref double[,] a, out int info, out matinvreport rep)
+    {
+        int n;
+        bool isupper;
+        if( (ap.cols(a)!=ap.rows(a)))
+            throw new alglibexception("Error while calling 'spdmatrixcholeskyinverse': looks like one of arguments has wrong size");
+        info = 0;
+        rep = new matinvreport();
+        n = ap.cols(a);
+        isupper = false;
+        matinv.spdmatrixcholeskyinverse(ref a, n, isupper, ref info, rep.innerobj);
+
+        return;
+    }
+
+    /*************************************************************************
+    Inversion of a symmetric positive definite matrix.
+
+    Given an upper or lower triangle of a symmetric positive definite matrix,
+    the algorithm generates matrix A^-1 and saves the upper or lower triangle
+    depending on the input.
+
+    Input parameters:
+        A       -   matrix to be inverted (upper or lower triangle).
+                    Array with elements [0..N-1,0..N-1].
+        N       -   size of matrix A (optional) :
+                    * if given, only principal NxN submatrix is processed  and
+                      overwritten. other elements are unchanged.
+                    * if not given,  size  is  automatically  determined  from
+                      matrix size (A must be square matrix)
+        IsUpper -   storage type (optional):
+                    * if True, symmetric  matrix  A  is  given  by  its  upper
+                      triangle, and the lower triangle isnt  used/changed  by
+                      function
+                    * if False,  symmetric matrix  A  is  given  by  its lower
+                      triangle, and the  upper triangle isnt used/changed  by
+                      function
+                    * if not given,  both lower and upper  triangles  must  be
+                      filled.
+
+    Output parameters:
+        Info    -   return code, same as in RMatrixLUInverse
+        Rep     -   solver report, same as in RMatrixLUInverse
+        A       -   inverse of matrix A, same as in RMatrixLUInverse
+
+      -- ALGLIB routine --
+         10.02.2010
+         Bochkanov Sergey
+    *************************************************************************/
+    public static void spdmatrixinverse(ref double[,] a, int n, bool isupper, out int info, out matinvreport rep)
+    {
+        info = 0;
+        rep = new matinvreport();
+        matinv.spdmatrixinverse(ref a, n, isupper, ref info, rep.innerobj);
+        return;
+    }
+    public static void spdmatrixinverse(ref double[,] a, out int info, out matinvreport rep)
+    {
+        int n;
+        bool isupper;
+        if( (ap.cols(a)!=ap.rows(a)))
+            throw new alglibexception("Error while calling 'spdmatrixinverse': looks like one of arguments has wrong size");
+        if( !alglib.ap.issymmetric(a) )
+            throw new alglibexception("'a' parameter is not symmetric matrix");
+        info = 0;
+        rep = new matinvreport();
+        n = ap.cols(a);
+        isupper = false;
+        matinv.spdmatrixinverse(ref a, n, isupper, ref info, rep.innerobj);
+        if( !alglib.ap.forcesymmetric(a) )
+            throw new alglibexception("Internal error while forcing symmetricity of 'a' parameter");
+        return;
+    }
+
+    /*************************************************************************
+    Inversion of a Hermitian positive definite matrix which is given
+    by Cholesky decomposition.
+
+    Input parameters:
+        A       -   Cholesky decomposition of the matrix to be inverted:
+                    A=U*U or A = L*L'.
+                    Output of  HPDMatrixCholesky subroutine.
+        N       -   size of matrix A (optional) :
+                    * if given, only principal NxN submatrix is processed  and
+                      overwritten. other elements are unchanged.
+                    * if not given,  size  is  automatically  determined  from
+                      matrix size (A must be square matrix)
+        IsUpper -   storage type (optional):
+                    * if True, symmetric  matrix  A  is  given  by  its  upper
+                      triangle, and the lower triangle isnt  used/changed  by
+                      function
+                    * if False,  symmetric matrix  A  is  given  by  its lower
+                      triangle, and the  upper triangle isnt used/changed  by
+                      function
+                    * if not given, lower half is used.
+
+    Output parameters:
+        Info    -   return code, same as in RMatrixLUInverse
+        Rep     -   solver report, same as in RMatrixLUInverse
+        A       -   inverse of matrix A, same as in RMatrixLUInverse
+
+      -- ALGLIB routine --
+         10.02.2010
+         Bochkanov Sergey
+    *************************************************************************/
+    public static void hpdmatrixcholeskyinverse(ref complex[,] a, int n, bool isupper, out int info, out matinvreport rep)
+    {
+        info = 0;
+        rep = new matinvreport();
+        matinv.hpdmatrixcholeskyinverse(ref a, n, isupper, ref info, rep.innerobj);
+        return;
+    }
+    public static void hpdmatrixcholeskyinverse(ref complex[,] a, out int info, out matinvreport rep)
+    {
+        int n;
+        bool isupper;
+        if( (ap.cols(a)!=ap.rows(a)))
+            throw new alglibexception("Error while calling 'hpdmatrixcholeskyinverse': looks like one of arguments has wrong size");
+        info = 0;
+        rep = new matinvreport();
+        n = ap.cols(a);
+        isupper = false;
+        matinv.hpdmatrixcholeskyinverse(ref a, n, isupper, ref info, rep.innerobj);
+
+        return;
+    }
+
+    /*************************************************************************
+    Inversion of a Hermitian positive definite matrix.
+
+    Given an upper or lower triangle of a Hermitian positive definite matrix,
+    the algorithm generates matrix A^-1 and saves the upper or lower triangle
+    depending on the input.
+
+    Input parameters:
+        A       -   matrix to be inverted (upper or lower triangle).
+                    Array with elements [0..N-1,0..N-1].
+        N       -   size of matrix A (optional) :
+                    * if given, only principal NxN submatrix is processed  and
+                      overwritten. other elements are unchanged.
+                    * if not given,  size  is  automatically  determined  from
+                      matrix size (A must be square matrix)
+        IsUpper -   storage type (optional):
+                    * if True, symmetric  matrix  A  is  given  by  its  upper
+                      triangle, and the lower triangle isnt  used/changed  by
+                      function
+                    * if False,  symmetric matrix  A  is  given  by  its lower
+                      triangle, and the  upper triangle isnt used/changed  by
+                      function
+                    * if not given,  both lower and upper  triangles  must  be
+                      filled.
+
+    Output parameters:
+        Info    -   return code, same as in RMatrixLUInverse
+        Rep     -   solver report, same as in RMatrixLUInverse
+        A       -   inverse of matrix A, same as in RMatrixLUInverse
+
+      -- ALGLIB routine --
+         10.02.2010
+         Bochkanov Sergey
+    *************************************************************************/
+    public static void hpdmatrixinverse(ref complex[,] a, int n, bool isupper, out int info, out matinvreport rep)
+    {
+        info = 0;
+        rep = new matinvreport();
+        matinv.hpdmatrixinverse(ref a, n, isupper, ref info, rep.innerobj);
+        return;
+    }
+    public static void hpdmatrixinverse(ref complex[,] a, out int info, out matinvreport rep)
+    {
+        int n;
+        bool isupper;
+        if( (ap.cols(a)!=ap.rows(a)))
+            throw new alglibexception("Error while calling 'hpdmatrixinverse': looks like one of arguments has wrong size");
+        if( !alglib.ap.ishermitian(a) )
+            throw new alglibexception("'a' parameter is not Hermitian matrix");
+        info = 0;
+        rep = new matinvreport();
+        n = ap.cols(a);
+        isupper = false;
+        matinv.hpdmatrixinverse(ref a, n, isupper, ref info, rep.innerobj);
+        if( !alglib.ap.forcehermitian(a) )
+            throw new alglibexception("Internal error while forcing Hermitian properties of 'a' parameter");
+        return;
+    }
+
+    /*************************************************************************
+    Triangular matrix inverse (real)
+
+    The subroutine inverts the following types of matrices:
+        * upper triangular
+        * upper triangular with unit diagonal
+        * lower triangular
+        * lower triangular with unit diagonal
+
+    In case of an upper (lower) triangular matrix,  the  inverse  matrix  will
+    also be upper (lower) triangular, and after the end of the algorithm,  the
+    inverse matrix replaces the source matrix. The elements  below (above) the
+    main diagonal are not changed by the algorithm.
+
+    If  the matrix  has a unit diagonal, the inverse matrix also  has  a  unit
+    diagonal, and the diagonal elements are not passed to the algorithm.
+
+    Input parameters:
+        A       -   matrix, array[0..N-1, 0..N-1].
+        N       -   size of matrix A (optional) :
+                    * if given, only principal NxN submatrix is processed  and
+                      overwritten. other elements are unchanged.
+                    * if not given,  size  is  automatically  determined  from
+                      matrix size (A must be square matrix)
+        IsUpper -   True, if the matrix is upper triangular.
+        IsUnit  -   diagonal type (optional):
+                    * if True, matrix has unit diagonal (a[i,i] are NOT used)
+                    * if False, matrix diagonal is arbitrary
+                    * if not given, False is assumed
+
+    Output parameters:
+        Info    -   same as for RMatrixLUInverse
+        Rep     -   same as for RMatrixLUInverse
+        A       -   same as for RMatrixLUInverse.
+
+      -- ALGLIB --
+         Copyright 05.02.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void rmatrixtrinverse(ref double[,] a, int n, bool isupper, bool isunit, out int info, out matinvreport rep)
+    {
+        info = 0;
+        rep = new matinvreport();
+        matinv.rmatrixtrinverse(ref a, n, isupper, isunit, ref info, rep.innerobj);
+        return;
+    }
+    public static void rmatrixtrinverse(ref double[,] a, bool isupper, out int info, out matinvreport rep)
+    {
+        int n;
+        bool isunit;
+        if( (ap.cols(a)!=ap.rows(a)))
+            throw new alglibexception("Error while calling 'rmatrixtrinverse': looks like one of arguments has wrong size");
+        info = 0;
+        rep = new matinvreport();
+        n = ap.cols(a);
+        isunit = false;
+        matinv.rmatrixtrinverse(ref a, n, isupper, isunit, ref info, rep.innerobj);
+
+        return;
+    }
+
+    /*************************************************************************
+    Triangular matrix inverse (complex)
+
+    The subroutine inverts the following types of matrices:
+        * upper triangular
+        * upper triangular with unit diagonal
+        * lower triangular
+        * lower triangular with unit diagonal
+
+    In case of an upper (lower) triangular matrix,  the  inverse  matrix  will
+    also be upper (lower) triangular, and after the end of the algorithm,  the
+    inverse matrix replaces the source matrix. The elements  below (above) the
+    main diagonal are not changed by the algorithm.
+
+    If  the matrix  has a unit diagonal, the inverse matrix also  has  a  unit
+    diagonal, and the diagonal elements are not passed to the algorithm.
+
+    Input parameters:
+        A       -   matrix, array[0..N-1, 0..N-1].
+        N       -   size of matrix A (optional) :
+                    * if given, only principal NxN submatrix is processed  and
+                      overwritten. other elements are unchanged.
+                    * if not given,  size  is  automatically  determined  from
+                      matrix size (A must be square matrix)
+        IsUpper -   True, if the matrix is upper triangular.
+        IsUnit  -   diagonal type (optional):
+                    * if True, matrix has unit diagonal (a[i,i] are NOT used)
+                    * if False, matrix diagonal is arbitrary
+                    * if not given, False is assumed
+
+    Output parameters:
+        Info    -   same as for RMatrixLUInverse
+        Rep     -   same as for RMatrixLUInverse
+        A       -   same as for RMatrixLUInverse.
+
+      -- ALGLIB --
+         Copyright 05.02.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void cmatrixtrinverse(ref complex[,] a, int n, bool isupper, bool isunit, out int info, out matinvreport rep)
+    {
+        info = 0;
+        rep = new matinvreport();
+        matinv.cmatrixtrinverse(ref a, n, isupper, isunit, ref info, rep.innerobj);
+        return;
+    }
+    public static void cmatrixtrinverse(ref complex[,] a, bool isupper, out int info, out matinvreport rep)
+    {
+        int n;
+        bool isunit;
+        if( (ap.cols(a)!=ap.rows(a)))
+            throw new alglibexception("Error while calling 'cmatrixtrinverse': looks like one of arguments has wrong size");
+        info = 0;
+        rep = new matinvreport();
+        n = ap.cols(a);
+        isunit = false;
+        matinv.cmatrixtrinverse(ref a, n, isupper, isunit, ref info, rep.innerobj);
+
+        return;
+    }
+
+}
+public partial class alglib
+{
+
+
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    Sparse matrix
+
+    You should use ALGLIB functions to work with sparse matrix.
+    Never try to access its fields directly!
+    *************************************************************************/
+    public class sparsematrix
+    {
+        //
+        // Public declarations
+        //
+
+        public sparsematrix()
+        {
+            _innerobj = new sparse.sparsematrix();
+        }
+
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private sparse.sparsematrix _innerobj;
+        public sparse.sparsematrix innerobj { get { return _innerobj; } }
+        public sparsematrix(sparse.sparsematrix obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+    /*************************************************************************
+    This function creates sparse matrix in a Hash-Table format.
+
+    This function creates Hast-Table matrix, which can be  converted  to  CRS
+    format after its initialization is over. Typical  usage  scenario  for  a
+    sparse matrix is:
+    1. creation in a Hash-Table format
+    2. insertion of the matrix elements
+    3. conversion to the CRS representation
+    4. matrix is passed to some linear algebra algorithm
+
+    Some  information  about  different matrix formats can be found below, in
+    the "NOTES" section.
+
+    INPUT PARAMETERS
+        M           -   number of rows in a matrix, M>=1
+        N           -   number of columns in a matrix, N>=1
+        K           -   K>=0, expected number of non-zero elements in a matrix.
+                        K can be inexact approximation, can be less than actual
+                        number  of  elements  (table will grow when needed) or
+                        even zero).
+                        It is important to understand that although hash-table
+                        may grow automatically, it is better to  provide  good
+                        estimate of data size.
+
+    OUTPUT PARAMETERS
+        S           -   sparse M*N matrix in Hash-Table representation.
+                        All elements of the matrix are zero.
+
+    NOTE 1.
+
+    Sparse matrices can be stored using either Hash-Table  representation  or
+    Compressed  Row  Storage  representation. Hast-table is better suited for
+    querying   and   dynamic   operations   (thus,  it  is  used  for  matrix
+    initialization), but it is inefficient when you want to make some  linear
+    algebra operations.
+
+    From the other side, CRS is better suited for linear algebra  operations,
+    but initialization is less convenient - you have to tell row sizes at the
+    initialization,  and  you  can  fill matrix only row by row, from left to
+    right. CRS is also very inefficient when you want to find matrix  element
+    by its index.
+
+    Thus,  Hash-Table  representation   does   not   support  linear  algebra
+    operations, while CRS format does not support modification of the  table.
+    Tables below outline information about these two formats:
+
+        OPERATIONS WITH MATRIX      HASH        CRS
+        create                      +           +
+        read element                +           +
+        modify element              +
+        add value to element        +
+        A*x  (dense vector)                     +
+        A'*x (dense vector)                     +
+        A*X  (dense matrix)                     +
+        A'*X (dense matrix)                     +
+
+    NOTE 2.
+
+    Hash-tables use memory inefficiently, and they have to keep  some  amount
+    of the "spare memory" in order to have good performance. Hash  table  for
+    matrix with K non-zero elements will  need  C*K*(8+2*sizeof(int))  bytes,
+    where C is a small constant, about 1.5-2 in magnitude.
+
+    CRS storage, from the other side, is  more  memory-efficient,  and  needs
+    just K*(8+sizeof(int))+M*sizeof(int) bytes, where M is a number  of  rows
+    in a matrix.
+
+    When you convert from the Hash-Table to CRS  representation, all unneeded
+    memory will be freed.
+
+      -- ALGLIB PROJECT --
+         Copyright 14.10.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void sparsecreate(int m, int n, int k, out sparsematrix s)
+    {
+        s = new sparsematrix();
+        sparse.sparsecreate(m, n, k, s.innerobj);
+        return;
+    }
+    public static void sparsecreate(int m, int n, out sparsematrix s)
+    {
+        int k;
+
+        s = new sparsematrix();
+        k = 0;
+        sparse.sparsecreate(m, n, k, s.innerobj);
+
+        return;
+    }
+
+    /*************************************************************************
+    This function creates sparse matrix in a CRS format (expert function for
+    situations when you are running out of memory).
+
+    This function creates CRS matrix. Typical usage scenario for a CRS matrix
+    is:
+    1. creation (you have to tell number of non-zero elements at each row  at
+       this moment)
+    2. insertion of the matrix elements (row by row, from left to right)
+    3. matrix is passed to some linear algebra algorithm
+
+    This function is a memory-efficient alternative to SparseCreate(), but it
+    is more complex because it requires you to know in advance how large your
+    matrix is. Some  information about  different matrix formats can be found
+    below, in the "NOTES" section.
+
+    INPUT PARAMETERS
+        M           -   number of rows in a matrix, M>=1
+        N           -   number of columns in a matrix, N>=1
+        NER         -   number of elements at each row, array[M], NER[i]>=0
+
+    OUTPUT PARAMETERS
+        S           -   sparse M*N matrix in CRS representation.
+                        You have to fill ALL non-zero elements by calling
+                        SparseSet() BEFORE you try to use this matrix.
+
+    NOTE 1.
+
+    Sparse matrices can be stored using either Hash-Table  representation  or
+    Compressed  Row  Storage  representation. Hast-table is better suited for
+    querying   and   dynamic   operations   (thus,  it  is  used  for  matrix
+    initialization), but it is inefficient when you want to make some  linear
+    algebra operations.
+
+    From the other side, CRS is better suited for linear algebra  operations,
+    but initialization is less convenient - you have to tell row sizes at the
+    initialization,  and  you  can  fill matrix only row by row, from left to
+    right. CRS is also very inefficient when you want to find matrix  element
+    by its index.
+
+    Thus,  Hash-Table  representation   does   not   support  linear  algebra
+    operations, while CRS format does not support modification of the  table.
+    Tables below outline information about these two formats:
+
+        OPERATIONS WITH MATRIX      HASH        CRS
+        create                      +           +
+        read element                +           +
+        modify element              +
+        add value to element        +
+        A*x  (dense vector)                     +
+        A'*x (dense vector)                     +
+        A*X  (dense matrix)                     +
+        A'*X (dense matrix)                     +
+
+    NOTE 2.
+
+    Hash-tables use memory inefficiently, and they have to keep  some  amount
+    of the "spare memory" in order to have good performance. Hash  table  for
+    matrix with K non-zero elements will  need  C*K*(8+2*sizeof(int))  bytes,
+    where C is a small constant, about 1.5-2 in magnitude.
+
+    CRS storage, from the other side, is  more  memory-efficient,  and  needs
+    just K*(8+sizeof(int))+M*sizeof(int) bytes, where M is a number  of  rows
+    in a matrix.
+
+    When you convert from the Hash-Table to CRS  representation, all unneeded
+    memory will be freed.
+
+      -- ALGLIB PROJECT --
+         Copyright 14.10.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void sparsecreatecrs(int m, int n, int[] ner, out sparsematrix s)
+    {
+        s = new sparsematrix();
+        sparse.sparsecreatecrs(m, n, ner, s.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    This function copies S0 to S1.
+
+    NOTE:  this  function  does  not verify its arguments, it just copies all
+    fields of the structure.
+
+      -- ALGLIB PROJECT --
+         Copyright 14.10.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void sparsecopy(sparsematrix s0, out sparsematrix s1)
+    {
+        s1 = new sparsematrix();
+        sparse.sparsecopy(s0.innerobj, s1.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    This function adds value to S[i,j] - element of the sparse matrix. Matrix
+    must be in a Hash-Table mode.
+
+    In case S[i,j] already exists in the table, V i added to  its  value.  In
+    case  S[i,j]  is  non-existent,  it  is  inserted  in  the  table.  Table
+    automatically grows when necessary.
+
+    INPUT PARAMETERS
+        S           -   sparse M*N matrix in Hash-Table representation.
+                        Exception will be thrown for CRS matrix.
+        I           -   row index of the element to modify, 0<=I<M
+        J           -   column index of the element to modify, 0<=J<N
+        V           -   value to add, must be finite number
+
+    OUTPUT PARAMETERS
+        S           -   modified matrix
+
+    NOTE 1:  when  S[i,j]  is exactly zero after modification, it is  deleted
+    from the table.
+
+      -- ALGLIB PROJECT --
+         Copyright 14.10.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void sparseadd(sparsematrix s, int i, int j, double v)
+    {
+
+        sparse.sparseadd(s.innerobj, i, j, v);
+        return;
+    }
+
+    /*************************************************************************
+    This function modifies S[i,j] - element of the sparse matrix. Matrix must
+    be in a Hash-Table mode.
+
+    In  case  new  value  of S[i,j] is zero, this element is deleted from the
+    table.
+
+    INPUT PARAMETERS
+        S           -   sparse M*N matrix in Hash-Table representation.
+                        Exception will be thrown for CRS matrix.
+        I           -   row index of the element to modify, 0<=I<M
+        J           -   column index of the element to modify, 0<=J<N
+        V           -   value to set, must be finite number, can be zero
+
+    OUTPUT PARAMETERS
+        S           -   modified matrix
+
+    NOTE:  this  function  has  no  effect  when  called with zero V for non-
+    existent element.
+
+      -- ALGLIB PROJECT --
+         Copyright 14.10.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void sparseset(sparsematrix s, int i, int j, double v)
+    {
+
+        sparse.sparseset(s.innerobj, i, j, v);
+        return;
+    }
+
+    /*************************************************************************
+    This function returns S[i,j] - element of the sparse matrix.  Matrix  can
+    be in any mode (Hash-Table or CRS), but this function is  less  efficient
+    for CRS matrices.  Hash-Table  matrices can  find element  in O(1)  time,
+    while  CRS  matrices  need  O(RS) time, where RS is an number of non-zero
+    elements in a row.
+
+    INPUT PARAMETERS
+        S           -   sparse M*N matrix in Hash-Table representation.
+                        Exception will be thrown for CRS matrix.
+        I           -   row index of the element to modify, 0<=I<M
+        J           -   column index of the element to modify, 0<=J<N
+
+    RESULT
+        value of S[I,J] or zero (in case no element with such index is found)
+
+      -- ALGLIB PROJECT --
+         Copyright 14.10.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static double sparseget(sparsematrix s, int i, int j)
+    {
+
+        double result = sparse.sparseget(s.innerobj, i, j);
+        return result;
+    }
+
+    /*************************************************************************
+    This function converts matrix to CRS format.
+
+    Some  algorithms  (linear  algebra ones, for example) require matrices in
+    CRS format.
+
+    INPUT PARAMETERS
+        S           -   sparse M*N matrix.
+
+    OUTPUT PARAMETERS
+        S           -   matrix in CRS format
+
+    NOTE:  this  function  has  no  effect  when  called with matrix which is
+    already in CRS mode.
+
+      -- ALGLIB PROJECT --
+         Copyright 14.10.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void sparseconverttocrs(sparsematrix s)
+    {
+
+        sparse.sparseconverttocrs(s.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    This function calculates matrix-vector product  S*x.  Matrix  S  must  be
+    stored in CRS format (exception will be thrown otherwise).
+
+    INPUT PARAMETERS
+        S           -   sparse M*N matrix in CRS format (you MUST convert  it
+                        to CRS before calling this function).
+        X           -   array[N], input vector. For  performance  reasons  we
+                        make only quick checks - we check that array size  is
+                        at least N, but we do not check for NAN's or INF's.
+        Y           -   output buffer, possibly preallocated. In case  buffer
+                        size is too small to store  result,  this  buffer  is
+                        automatically resized.
+
+    OUTPUT PARAMETERS
+        Y           -   array[M], S*x
+
+    NOTE: this function throws exception when called for non-CRS matrix.  You
+    must convert your matrix  with  SparseConvertToCRS()  before  using  this
+    function.
+
+      -- ALGLIB PROJECT --
+         Copyright 14.10.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void sparsemv(sparsematrix s, double[] x, ref double[] y)
+    {
+
+        sparse.sparsemv(s.innerobj, x, ref y);
+        return;
+    }
+
+    /*************************************************************************
+    This function calculates matrix-vector product  S^T*x. Matrix S  must  be
+    stored in CRS format (exception will be thrown otherwise).
+
+    INPUT PARAMETERS
+        S           -   sparse M*N matrix in CRS format (you MUST convert  it
+                        to CRS before calling this function).
+        X           -   array[M], input vector. For  performance  reasons  we
+                        make only quick checks - we check that array size  is
+                        at least M, but we do not check for NAN's or INF's.
+        Y           -   output buffer, possibly preallocated. In case  buffer
+                        size is too small to store  result,  this  buffer  is
+                        automatically resized.
+
+    OUTPUT PARAMETERS
+        Y           -   array[N], S^T*x
+
+    NOTE: this function throws exception when called for non-CRS matrix.  You
+    must convert your matrix  with  SparseConvertToCRS()  before  using  this
+    function.
+
+      -- ALGLIB PROJECT --
+         Copyright 14.10.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void sparsemtv(sparsematrix s, double[] x, ref double[] y)
+    {
+
+        sparse.sparsemtv(s.innerobj, x, ref y);
+        return;
+    }
+
+    /*************************************************************************
+    This function simultaneously calculates two matrix-vector products:
+        S*x and S^T*x.
+    S must be square (non-rectangular) matrix stored in CRS format (exception
+    will be thrown otherwise).
+
+    INPUT PARAMETERS
+        S           -   sparse N*N matrix in CRS format (you MUST convert  it
+                        to CRS before calling this function).
+        X           -   array[N], input vector. For  performance  reasons  we
+                        make only quick checks - we check that array size  is
+                        at least N, but we do not check for NAN's or INF's.
+        Y0          -   output buffer, possibly preallocated. In case  buffer
+                        size is too small to store  result,  this  buffer  is
+                        automatically resized.
+        Y1          -   output buffer, possibly preallocated. In case  buffer
+                        size is too small to store  result,  this  buffer  is
+                        automatically resized.
+
+    OUTPUT PARAMETERS
+        Y0          -   array[N], S*x
+        Y1          -   array[N], S^T*x
+
+    NOTE: this function throws exception when called for non-CRS matrix.  You
+    must convert your matrix  with  SparseConvertToCRS()  before  using  this
+    function. It also throws exception when S is non-square.
+
+      -- ALGLIB PROJECT --
+         Copyright 14.10.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void sparsemv2(sparsematrix s, double[] x, ref double[] y0, ref double[] y1)
+    {
+
+        sparse.sparsemv2(s.innerobj, x, ref y0, ref y1);
+        return;
+    }
+
+    /*************************************************************************
+    This function calculates matrix-vector product  S*x, when S is  symmetric
+    matrix.  Matrix  S  must  be stored in  CRS  format  (exception  will  be
+    thrown otherwise).
+
+    INPUT PARAMETERS
+        S           -   sparse M*M matrix in CRS format (you MUST convert  it
+                        to CRS before calling this function).
+        X           -   array[N], input vector. For  performance  reasons  we
+                        make only quick checks - we check that array size  is
+                        at least N, but we do not check for NAN's or INF's.
+        Y           -   output buffer, possibly preallocated. In case  buffer
+                        size is too small to store  result,  this  buffer  is
+                        automatically resized.
+
+    OUTPUT PARAMETERS
+        Y           -   array[M], S*x
+
+    NOTE: this function throws exception when called for non-CRS matrix.  You
+    must convert your matrix  with  SparseConvertToCRS()  before  using  this
+    function.
+
+      -- ALGLIB PROJECT --
+         Copyright 14.10.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void sparsesmv(sparsematrix s, bool isupper, double[] x, ref double[] y)
+    {
+
+        sparse.sparsesmv(s.innerobj, isupper, x, ref y);
+        return;
+    }
+
+    /*************************************************************************
+    This function calculates matrix-matrix product  S*A.  Matrix  S  must  be
+    stored in CRS format (exception will be thrown otherwise).
+
+    INPUT PARAMETERS
+        S           -   sparse M*N matrix in CRS format (you MUST convert  it
+                        to CRS before calling this function).
+        A           -   array[N][K], input dense matrix. For  performance reasons
+                        we make only quick checks - we check that array size
+                        is at least N, but we do not check for NAN's or INF's.
+        K           -   number of columns of matrix (A).
+        B           -   output buffer, possibly preallocated. In case  buffer
+                        size is too small to store  result,  this  buffer  is
+                        automatically resized.
+
+    OUTPUT PARAMETERS
+        B           -   array[M][K], S*A
+
+    NOTE: this function throws exception when called for non-CRS matrix.  You
+    must convert your matrix  with  SparseConvertToCRS()  before  using  this
+    function.
+
+      -- ALGLIB PROJECT --
+         Copyright 14.10.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void sparsemm(sparsematrix s, double[,] a, int k, ref double[,] b)
+    {
+
+        sparse.sparsemm(s.innerobj, a, k, ref b);
+        return;
+    }
+
+    /*************************************************************************
+    This function calculates matrix-matrix product  S^T*A. Matrix S  must  be
+    stored in CRS format (exception will be thrown otherwise).
+
+    INPUT PARAMETERS
+        S           -   sparse M*N matrix in CRS format (you MUST convert  it
+                        to CRS before calling this function).
+        A           -   array[M][K], input dense matrix. For performance reasons
+                        we make only quick checks - we check that array size  is
+                        at least M, but we do not check for NAN's or INF's.
+        K           -   number of columns of matrix (A).
+        B           -   output buffer, possibly preallocated. In case  buffer
+                        size is too small to store  result,  this  buffer  is
+                        automatically resized.
+
+    OUTPUT PARAMETERS
+        B           -   array[N][K], S^T*A
+
+    NOTE: this function throws exception when called for non-CRS matrix.  You
+    must convert your matrix  with  SparseConvertToCRS()  before  using  this
+    function.
+
+      -- ALGLIB PROJECT --
+         Copyright 14.10.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void sparsemtm(sparsematrix s, double[,] a, int k, ref double[,] b)
+    {
+
+        sparse.sparsemtm(s.innerobj, a, k, ref b);
+        return;
+    }
+
+    /*************************************************************************
+    This function simultaneously calculates two matrix-matrix products:
+        S*A and S^T*A.
+    S must be square (non-rectangular) matrix stored in CRS format (exception
+    will be thrown otherwise).
+
+    INPUT PARAMETERS
+        S           -   sparse N*N matrix in CRS format (you MUST convert  it
+                        to CRS before calling this function).
+        A           -   array[N][K], input dense matrix. For performance reasons
+                        we make only quick checks - we check that array size  is
+                        at least N, but we do not check for NAN's or INF's.
+        K           -   number of columns of matrix (A).
+        B0          -   output buffer, possibly preallocated. In case  buffer
+                        size is too small to store  result,  this  buffer  is
+                        automatically resized.
+        B1          -   output buffer, possibly preallocated. In case  buffer
+                        size is too small to store  result,  this  buffer  is
+                        automatically resized.
+
+    OUTPUT PARAMETERS
+        B0          -   array[N][K], S*A
+        B1          -   array[N][K], S^T*A
+
+    NOTE: this function throws exception when called for non-CRS matrix.  You
+    must convert your matrix  with  SparseConvertToCRS()  before  using  this
+    function. It also throws exception when S is non-square.
+
+      -- ALGLIB PROJECT --
+         Copyright 14.10.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void sparsemm2(sparsematrix s, double[,] a, int k, ref double[,] b0, ref double[,] b1)
+    {
+
+        sparse.sparsemm2(s.innerobj, a, k, ref b0, ref b1);
+        return;
+    }
+
+    /*************************************************************************
+    This function calculates matrix-matrix product  S*A, when S  is  symmetric
+    matrix.  Matrix  S  must  be stored  in  CRS  format  (exception  will  be
+    thrown otherwise).
+
+    INPUT PARAMETERS
+        S           -   sparse M*M matrix in CRS format (you MUST convert  it
+                        to CRS before calling this function).
+        A           -   array[N][K], input dense matrix. For performance reasons
+                        we make only quick checks - we check that array size is
+                        at least N, but we do not check for NAN's or INF's.
+        K           -   number of columns of matrix (A).
+        B           -   output buffer, possibly preallocated. In case  buffer
+                        size is too small to store  result,  this  buffer  is
+                        automatically resized.
+
+    OUTPUT PARAMETERS
+        B           -   array[M][K], S*A
+
+    NOTE: this function throws exception when called for non-CRS matrix.  You
+    must convert your matrix  with  SparseConvertToCRS()  before  using  this
+    function.
+
+      -- ALGLIB PROJECT --
+         Copyright 14.10.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void sparsesmm(sparsematrix s, bool isupper, double[,] a, int k, ref double[,] b)
+    {
+
+        sparse.sparsesmm(s.innerobj, isupper, a, k, ref b);
+        return;
+    }
+
+    /*************************************************************************
+    This procedure resizes Hash-Table matrix. It can be called when you  have
+    deleted too many elements from the matrix, and you want to  free unneeded
+    memory.
+
+      -- ALGLIB PROJECT --
+         Copyright 14.10.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void sparseresizematrix(sparsematrix s)
+    {
+
+        sparse.sparseresizematrix(s.innerobj);
+        return;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    This object stores state of the iterative norm estimation algorithm.
+
+    You should use ALGLIB functions to work with this object.
+    *************************************************************************/
+    public class normestimatorstate
+    {
+        //
+        // Public declarations
+        //
+
+        public normestimatorstate()
+        {
+            _innerobj = new normestimator.normestimatorstate();
+        }
+
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private normestimator.normestimatorstate _innerobj;
+        public normestimator.normestimatorstate innerobj { get { return _innerobj; } }
+        public normestimatorstate(normestimator.normestimatorstate obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+    /*************************************************************************
+    This procedure initializes matrix norm estimator.
+
+    USAGE:
+    1. User initializes algorithm state with NormEstimatorCreate() call
+    2. User calls NormEstimatorEstimateSparse() (or NormEstimatorIteration())
+    3. User calls NormEstimatorResults() to get solution.
+
+    INPUT PARAMETERS:
+        M       -   number of rows in the matrix being estimated, M>0
+        N       -   number of columns in the matrix being estimated, N>0
+        NStart  -   number of random starting vectors
+                    recommended value - at least 5.
+        NIts    -   number of iterations to do with best starting vector
+                    recommended value - at least 5.
+
+    OUTPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+
+
+    NOTE: this algorithm is effectively deterministic, i.e. it always  returns
+    same result when repeatedly called for the same matrix. In fact, algorithm
+    uses randomized starting vectors, but internal  random  numbers  generator
+    always generates same sequence of the random values (it is a  feature, not
+    bug).
+
+    Algorithm can be made non-deterministic with NormEstimatorSetSeed(0) call.
+
+      -- ALGLIB --
+         Copyright 06.12.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void normestimatorcreate(int m, int n, int nstart, int nits, out normestimatorstate state)
+    {
+        state = new normestimatorstate();
+        normestimator.normestimatorcreate(m, n, nstart, nits, state.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    This function changes seed value used by algorithm. In some cases we  need
+    deterministic processing, i.e. subsequent calls must return equal results,
+    in other cases we need non-deterministic algorithm which returns different
+    results for the same matrix on every pass.
+
+    Setting zero seed will lead to non-deterministic algorithm, while non-zero
+    value will make our algorithm deterministic.
+
+    INPUT PARAMETERS:
+        State       -   norm estimator state, must be initialized with a  call
+                        to NormEstimatorCreate()
+        SeedVal     -   seed value, >=0. Zero value = non-deterministic algo.
+
+      -- ALGLIB --
+         Copyright 06.12.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void normestimatorsetseed(normestimatorstate state, int seedval)
+    {
+
+        normestimator.normestimatorsetseed(state.innerobj, seedval);
+        return;
+    }
+
+    /*************************************************************************
+    This function estimates norm of the sparse M*N matrix A.
+
+    INPUT PARAMETERS:
+        State       -   norm estimator state, must be initialized with a  call
+                        to NormEstimatorCreate()
+        A           -   sparse M*N matrix, must be converted to CRS format
+                        prior to calling this function.
+
+    After this function  is  over  you can call NormEstimatorResults() to get
+    estimate of the norm(A).
+
+      -- ALGLIB --
+         Copyright 06.12.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void normestimatorestimatesparse(normestimatorstate state, sparsematrix a)
+    {
+
+        normestimator.normestimatorestimatesparse(state.innerobj, a.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    Matrix norm estimation results
+
+    INPUT PARAMETERS:
+        State   -   algorithm state
+
+    OUTPUT PARAMETERS:
+        Nrm     -   estimate of the matrix norm, Nrm>=0
+
+      -- ALGLIB --
+         Copyright 06.12.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void normestimatorresults(normestimatorstate state, out double nrm)
+    {
+        nrm = 0;
+        normestimator.normestimatorresults(state.innerobj, ref nrm);
+        return;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    Determinant calculation of the matrix given by its LU decomposition.
+
+    Input parameters:
+        A       -   LU decomposition of the matrix (output of
+                    RMatrixLU subroutine).
+        Pivots  -   table of permutations which were made during
+                    the LU decomposition.
+                    Output of RMatrixLU subroutine.
+        N       -   (optional) size of matrix A:
+                    * if given, only principal NxN submatrix is processed and
+                      overwritten. other elements are unchanged.
+                    * if not given, automatically determined from matrix size
+                      (A must be square matrix)
+
+    Result: matrix determinant.
+
+      -- ALGLIB --
+         Copyright 2005 by Bochkanov Sergey
+    *************************************************************************/
+    public static double rmatrixludet(double[,] a, int[] pivots, int n)
+    {
+
+        double result = matdet.rmatrixludet(a, pivots, n);
+        return result;
+    }
+    public static double rmatrixludet(double[,] a, int[] pivots)
+    {
+        int n;
+        if( (ap.rows(a)!=ap.cols(a)) || (ap.rows(a)!=ap.len(pivots)))
+            throw new alglibexception("Error while calling 'rmatrixludet': looks like one of arguments has wrong size");
+
+        n = ap.rows(a);
+        double result = matdet.rmatrixludet(a, pivots, n);
+
+        return result;
+    }
+
+    /*************************************************************************
+    Calculation of the determinant of a general matrix
+
+    Input parameters:
+        A       -   matrix, array[0..N-1, 0..N-1]
+        N       -   (optional) size of matrix A:
+                    * if given, only principal NxN submatrix is processed and
+                      overwritten. other elements are unchanged.
+                    * if not given, automatically determined from matrix size
+                      (A must be square matrix)
+
+    Result: determinant of matrix A.
+
+      -- ALGLIB --
+         Copyright 2005 by Bochkanov Sergey
+    *************************************************************************/
+    public static double rmatrixdet(double[,] a, int n)
+    {
+
+        double result = matdet.rmatrixdet(a, n);
+        return result;
+    }
+    public static double rmatrixdet(double[,] a)
+    {
+        int n;
+        if( (ap.rows(a)!=ap.cols(a)))
+            throw new alglibexception("Error while calling 'rmatrixdet': looks like one of arguments has wrong size");
+
+        n = ap.rows(a);
+        double result = matdet.rmatrixdet(a, n);
+
+        return result;
+    }
+
+    /*************************************************************************
+    Determinant calculation of the matrix given by its LU decomposition.
+
+    Input parameters:
+        A       -   LU decomposition of the matrix (output of
+                    RMatrixLU subroutine).
+        Pivots  -   table of permutations which were made during
+                    the LU decomposition.
+                    Output of RMatrixLU subroutine.
+        N       -   (optional) size of matrix A:
+                    * if given, only principal NxN submatrix is processed and
+                      overwritten. other elements are unchanged.
+                    * if not given, automatically determined from matrix size
+                      (A must be square matrix)
+
+    Result: matrix determinant.
+
+      -- ALGLIB --
+         Copyright 2005 by Bochkanov Sergey
+    *************************************************************************/
+    public static complex cmatrixludet(complex[,] a, int[] pivots, int n)
+    {
+
+        complex result = matdet.cmatrixludet(a, pivots, n);
+        return result;
+    }
+    public static complex cmatrixludet(complex[,] a, int[] pivots)
+    {
+        int n;
+        if( (ap.rows(a)!=ap.cols(a)) || (ap.rows(a)!=ap.len(pivots)))
+            throw new alglibexception("Error while calling 'cmatrixludet': looks like one of arguments has wrong size");
+
+        n = ap.rows(a);
+        complex result = matdet.cmatrixludet(a, pivots, n);
+
+        return result;
+    }
+
+    /*************************************************************************
+    Calculation of the determinant of a general matrix
+
+    Input parameters:
+        A       -   matrix, array[0..N-1, 0..N-1]
+        N       -   (optional) size of matrix A:
+                    * if given, only principal NxN submatrix is processed and
+                      overwritten. other elements are unchanged.
+                    * if not given, automatically determined from matrix size
+                      (A must be square matrix)
+
+    Result: determinant of matrix A.
+
+      -- ALGLIB --
+         Copyright 2005 by Bochkanov Sergey
+    *************************************************************************/
+    public static complex cmatrixdet(complex[,] a, int n)
+    {
+
+        complex result = matdet.cmatrixdet(a, n);
+        return result;
+    }
+    public static complex cmatrixdet(complex[,] a)
+    {
+        int n;
+        if( (ap.rows(a)!=ap.cols(a)))
+            throw new alglibexception("Error while calling 'cmatrixdet': looks like one of arguments has wrong size");
+
+        n = ap.rows(a);
+        complex result = matdet.cmatrixdet(a, n);
+
+        return result;
+    }
+
+    /*************************************************************************
+    Determinant calculation of the matrix given by the Cholesky decomposition.
+
+    Input parameters:
+        A       -   Cholesky decomposition,
+                    output of SMatrixCholesky subroutine.
+        N       -   (optional) size of matrix A:
+                    * if given, only principal NxN submatrix is processed and
+                      overwritten. other elements are unchanged.
+                    * if not given, automatically determined from matrix size
+                      (A must be square matrix)
+
+    As the determinant is equal to the product of squares of diagonal elements,
+    its not necessary to specify which triangle - lower or upper - the matrix
+    is stored in.
+
+    Result:
+        matrix determinant.
+
+      -- ALGLIB --
+         Copyright 2005-2008 by Bochkanov Sergey
+    *************************************************************************/
+    public static double spdmatrixcholeskydet(double[,] a, int n)
+    {
+
+        double result = matdet.spdmatrixcholeskydet(a, n);
+        return result;
+    }
+    public static double spdmatrixcholeskydet(double[,] a)
+    {
+        int n;
+        if( (ap.rows(a)!=ap.cols(a)))
+            throw new alglibexception("Error while calling 'spdmatrixcholeskydet': looks like one of arguments has wrong size");
+
+        n = ap.rows(a);
+        double result = matdet.spdmatrixcholeskydet(a, n);
+
+        return result;
+    }
+
+    /*************************************************************************
+    Determinant calculation of the symmetric positive definite matrix.
+
+    Input parameters:
+        A       -   matrix. Array with elements [0..N-1, 0..N-1].
+        N       -   (optional) size of matrix A:
+                    * if given, only principal NxN submatrix is processed and
+                      overwritten. other elements are unchanged.
+                    * if not given, automatically determined from matrix size
+                      (A must be square matrix)
+        IsUpper -   (optional) storage type:
+                    * if True, symmetric matrix  A  is  given  by  its  upper
+                      triangle, and the lower triangle isnt used/changed  by
+                      function
+                    * if False, symmetric matrix  A  is  given  by  its lower
+                      triangle, and the upper triangle isnt used/changed  by
+                      function
+                    * if not given, both lower and upper  triangles  must  be
+                      filled.
+
+    Result:
+        determinant of matrix A.
+        If matrix A is not positive definite, exception is thrown.
+
+      -- ALGLIB --
+         Copyright 2005-2008 by Bochkanov Sergey
+    *************************************************************************/
+    public static double spdmatrixdet(double[,] a, int n, bool isupper)
+    {
+
+        double result = matdet.spdmatrixdet(a, n, isupper);
+        return result;
+    }
+    public static double spdmatrixdet(double[,] a)
+    {
+        int n;
+        bool isupper;
+        if( (ap.rows(a)!=ap.cols(a)))
+            throw new alglibexception("Error while calling 'spdmatrixdet': looks like one of arguments has wrong size");
+        if( !alglib.ap.issymmetric(a) )
+            throw new alglibexception("'a' parameter is not symmetric matrix");
+
+        n = ap.rows(a);
+        isupper = false;
+        double result = matdet.spdmatrixdet(a, n, isupper);
+
+        return result;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    Algorithm for solving the following generalized symmetric positive-definite
+    eigenproblem:
+        A*x = lambda*B*x (1) or
+        A*B*x = lambda*x (2) or
+        B*A*x = lambda*x (3).
+    where A is a symmetric matrix, B - symmetric positive-definite matrix.
+    The problem is solved by reducing it to an ordinary  symmetric  eigenvalue
+    problem.
+
+    Input parameters:
+        A           -   symmetric matrix which is given by its upper or lower
+                        triangular part.
+                        Array whose indexes range within [0..N-1, 0..N-1].
+        N           -   size of matrices A and B.
+        IsUpperA    -   storage format of matrix A.
+        B           -   symmetric positive-definite matrix which is given by
+                        its upper or lower triangular part.
+                        Array whose indexes range within [0..N-1, 0..N-1].
+        IsUpperB    -   storage format of matrix B.
+        ZNeeded     -   if ZNeeded is equal to:
+                         * 0, the eigenvectors are not returned;
+                         * 1, the eigenvectors are returned.
+        ProblemType -   if ProblemType is equal to:
+                         * 1, the following problem is solved: A*x = lambda*B*x;
+                         * 2, the following problem is solved: A*B*x = lambda*x;
+                         * 3, the following problem is solved: B*A*x = lambda*x.
+
+    Output parameters:
+        D           -   eigenvalues in ascending order.
+                        Array whose index ranges within [0..N-1].
+        Z           -   if ZNeeded is equal to:
+                         * 0, Z hasnt changed;
+                         * 1, Z contains eigenvectors.
+                        Array whose indexes range within [0..N-1, 0..N-1].
+                        The eigenvectors are stored in matrix columns. It should
+                        be noted that the eigenvectors in such problems do not
+                        form an orthogonal system.
+
+    Result:
+        True, if the problem was solved successfully.
+        False, if the error occurred during the Cholesky decomposition of matrix
+        B (the matrix isnt positive-definite) or during the work of the iterative
+        algorithm for solving the symmetric eigenproblem.
+
+    See also the GeneralizedSymmetricDefiniteEVDReduce subroutine.
+
+      -- ALGLIB --
+         Copyright 1.28.2006 by Bochkanov Sergey
+    *************************************************************************/
+    public static bool smatrixgevd(double[,] a, int n, bool isuppera, double[,] b, bool isupperb, int zneeded, int problemtype, out double[] d, out double[,] z)
+    {
+        d = new double[0];
+        z = new double[0,0];
+        bool result = spdgevd.smatrixgevd(a, n, isuppera, b, isupperb, zneeded, problemtype, ref d, ref z);
+        return result;
+    }
+
+    /*************************************************************************
+    Algorithm for reduction of the following generalized symmetric positive-
+    definite eigenvalue problem:
+        A*x = lambda*B*x (1) or
+        A*B*x = lambda*x (2) or
+        B*A*x = lambda*x (3)
+    to the symmetric eigenvalues problem C*y = lambda*y (eigenvalues of this and
+    the given problems are the same, and the eigenvectors of the given problem
+    could be obtained by multiplying the obtained eigenvectors by the
+    transformation matrix x = R*y).
+
+    Here A is a symmetric matrix, B - symmetric positive-definite matrix.
+
+    Input parameters:
+        A           -   symmetric matrix which is given by its upper or lower
+                        triangular part.
+                        Array whose indexes range within [0..N-1, 0..N-1].
+        N           -   size of matrices A and B.
+        IsUpperA    -   storage format of matrix A.
+        B           -   symmetric positive-definite matrix which is given by
+                        its upper or lower triangular part.
+                        Array whose indexes range within [0..N-1, 0..N-1].
+        IsUpperB    -   storage format of matrix B.
+        ProblemType -   if ProblemType is equal to:
+                         * 1, the following problem is solved: A*x = lambda*B*x;
+                         * 2, the following problem is solved: A*B*x = lambda*x;
+                         * 3, the following problem is solved: B*A*x = lambda*x.
+
+    Output parameters:
+        A           -   symmetric matrix which is given by its upper or lower
+                        triangle depending on IsUpperA. Contains matrix C.
+                        Array whose indexes range within [0..N-1, 0..N-1].
+        R           -   upper triangular or low triangular transformation matrix
+                        which is used to obtain the eigenvectors of a given problem
+                        as the product of eigenvectors of C (from the right) and
+                        matrix R (from the left). If the matrix is upper
+                        triangular, the elements below the main diagonal
+                        are equal to 0 (and vice versa). Thus, we can perform
+                        the multiplication without taking into account the
+                        internal structure (which is an easier though less
+                        effective way).
+                        Array whose indexes range within [0..N-1, 0..N-1].
+        IsUpperR    -   type of matrix R (upper or lower triangular).
+
+    Result:
+        True, if the problem was reduced successfully.
+        False, if the error occurred during the Cholesky decomposition of
+            matrix B (the matrix is not positive-definite).
+
+      -- ALGLIB --
+         Copyright 1.28.2006 by Bochkanov Sergey
+    *************************************************************************/
+    public static bool smatrixgevdreduce(ref double[,] a, int n, bool isuppera, double[,] b, bool isupperb, int problemtype, out double[,] r, out bool isupperr)
+    {
+        r = new double[0,0];
+        isupperr = false;
+        bool result = spdgevd.smatrixgevdreduce(ref a, n, isuppera, b, isupperb, problemtype, ref r, ref isupperr);
+        return result;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    Inverse matrix update by the Sherman-Morrison formula
+
+    The algorithm updates matrix A^-1 when adding a number to an element
+    of matrix A.
+
+    Input parameters:
+        InvA    -   inverse of matrix A.
+                    Array whose indexes range within [0..N-1, 0..N-1].
+        N       -   size of matrix A.
+        UpdRow  -   row where the element to be updated is stored.
+        UpdColumn - column where the element to be updated is stored.
+        UpdVal  -   a number to be added to the element.
+
+
+    Output parameters:
+        InvA    -   inverse of modified matrix A.
+
+      -- ALGLIB --
+         Copyright 2005 by Bochkanov Sergey
+    *************************************************************************/
+    public static void rmatrixinvupdatesimple(ref double[,] inva, int n, int updrow, int updcolumn, double updval)
+    {
+
+        inverseupdate.rmatrixinvupdatesimple(ref inva, n, updrow, updcolumn, updval);
+        return;
+    }
+
+    /*************************************************************************
+    Inverse matrix update by the Sherman-Morrison formula
+
+    The algorithm updates matrix A^-1 when adding a vector to a row
+    of matrix A.
+
+    Input parameters:
+        InvA    -   inverse of matrix A.
+                    Array whose indexes range within [0..N-1, 0..N-1].
+        N       -   size of matrix A.
+        UpdRow  -   the row of A whose vector V was added.
+                    0 <= Row <= N-1
+        V       -   the vector to be added to a row.
+                    Array whose index ranges within [0..N-1].
+
+    Output parameters:
+        InvA    -   inverse of modified matrix A.
+
+      -- ALGLIB --
+         Copyright 2005 by Bochkanov Sergey
+    *************************************************************************/
+    public static void rmatrixinvupdaterow(ref double[,] inva, int n, int updrow, double[] v)
+    {
+
+        inverseupdate.rmatrixinvupdaterow(ref inva, n, updrow, v);
+        return;
+    }
+
+    /*************************************************************************
+    Inverse matrix update by the Sherman-Morrison formula
+
+    The algorithm updates matrix A^-1 when adding a vector to a column
+    of matrix A.
+
+    Input parameters:
+        InvA        -   inverse of matrix A.
+                        Array whose indexes range within [0..N-1, 0..N-1].
+        N           -   size of matrix A.
+        UpdColumn   -   the column of A whose vector U was added.
+                        0 <= UpdColumn <= N-1
+        U           -   the vector to be added to a column.
+                        Array whose index ranges within [0..N-1].
+
+    Output parameters:
+        InvA        -   inverse of modified matrix A.
+
+      -- ALGLIB --
+         Copyright 2005 by Bochkanov Sergey
+    *************************************************************************/
+    public static void rmatrixinvupdatecolumn(ref double[,] inva, int n, int updcolumn, double[] u)
+    {
+
+        inverseupdate.rmatrixinvupdatecolumn(ref inva, n, updcolumn, u);
+        return;
+    }
+
+    /*************************************************************************
+    Inverse matrix update by the Sherman-Morrison formula
+
+    The algorithm computes the inverse of matrix A+u*v by using the given matrix
+    A^-1 and the vectors u and v.
+
+    Input parameters:
+        InvA    -   inverse of matrix A.
+                    Array whose indexes range within [0..N-1, 0..N-1].
+        N       -   size of matrix A.
+        U       -   the vector modifying the matrix.
+                    Array whose index ranges within [0..N-1].
+        V       -   the vector modifying the matrix.
+                    Array whose index ranges within [0..N-1].
+
+    Output parameters:
+        InvA - inverse of matrix A + u*v'.
+
+      -- ALGLIB --
+         Copyright 2005 by Bochkanov Sergey
+    *************************************************************************/
+    public static void rmatrixinvupdateuv(ref double[,] inva, int n, double[] u, double[] v)
+    {
+
+        inverseupdate.rmatrixinvupdateuv(ref inva, n, u, v);
+        return;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    Subroutine performing the Schur decomposition of a general matrix by using
+    the QR algorithm with multiple shifts.
+
+    The source matrix A is represented as S'*A*S = T, where S is an orthogonal
+    matrix (Schur vectors), T - upper quasi-triangular matrix (with blocks of
+    sizes 1x1 and 2x2 on the main diagonal).
+
+    Input parameters:
+        A   -   matrix to be decomposed.
+                Array whose indexes range within [0..N-1, 0..N-1].
+        N   -   size of A, N>=0.
+
+
+    Output parameters:
+        A   -   contains matrix T.
+                Array whose indexes range within [0..N-1, 0..N-1].
+        S   -   contains Schur vectors.
+                Array whose indexes range within [0..N-1, 0..N-1].
+
+    Note 1:
+        The block structure of matrix T can be easily recognized: since all
+        the elements below the blocks are zeros, the elements a[i+1,i] which
+        are equal to 0 show the block border.
+
+    Note 2:
+        The algorithm performance depends on the value of the internal parameter
+        NS of the InternalSchurDecomposition subroutine which defines the number
+        of shifts in the QR algorithm (similarly to the block width in block-matrix
+        algorithms in linear algebra). If you require maximum performance on
+        your machine, it is recommended to adjust this parameter manually.
+
+    Result:
+        True,
+            if the algorithm has converged and parameters A and S contain the result.
+        False,
+            if the algorithm has not converged.
+
+    Algorithm implemented on the basis of the DHSEQR subroutine (LAPACK 3.0 library).
+    *************************************************************************/
+    public static bool rmatrixschur(ref double[,] a, int n, out double[,] s)
+    {
+        s = new double[0,0];
+        bool result = schur.rmatrixschur(ref a, n, ref s);
+        return result;
+    }
+
+}
+public partial class alglib
+{
+    public class ablas
+    {
+        /*************************************************************************
+        Splits matrix length in two parts, left part should match ABLAS block size
+
+        INPUT PARAMETERS
+            A   -   real matrix, is passed to ensure that we didn't split
+                    complex matrix using real splitting subroutine.
+                    matrix itself is not changed.
+            N   -   length, N>0
+
+        OUTPUT PARAMETERS
+            N1  -   length
+            N2  -   length
+
+        N1+N2=N, N1>=N2, N2 may be zero
+
+          -- ALGLIB routine --
+             15.12.2009
+             Bochkanov Sergey
+        *************************************************************************/
+        public static void ablassplitlength(double[,] a,
+            int n,
+            ref int n1,
+            ref int n2)
+        {
+            n1 = 0;
+            n2 = 0;
+
+            if( n>ablasblocksize(a) )
+            {
+                ablasinternalsplitlength(n, ablasblocksize(a), ref n1, ref n2);
+            }
+            else
+            {
+                ablasinternalsplitlength(n, ablasmicroblocksize(), ref n1, ref n2);
+            }
+        }
+
+
+        /*************************************************************************
+        Complex ABLASSplitLength
+
+          -- ALGLIB routine --
+             15.12.2009
+             Bochkanov Sergey
+        *************************************************************************/
+        public static void ablascomplexsplitlength(complex[,] a,
+            int n,
+            ref int n1,
+            ref int n2)
+        {
+            n1 = 0;
+            n2 = 0;
+
+            if( n>ablascomplexblocksize(a) )
+            {
+                ablasinternalsplitlength(n, ablascomplexblocksize(a), ref n1, ref n2);
+            }
+            else
+            {
+                ablasinternalsplitlength(n, ablasmicroblocksize(), ref n1, ref n2);
+            }
+        }
+
+
+        /*************************************************************************
+        Returns block size - subdivision size where  cache-oblivious  soubroutines
+        switch to the optimized kernel.
+
+        INPUT PARAMETERS
+            A   -   real matrix, is passed to ensure that we didn't split
+                    complex matrix using real splitting subroutine.
+                    matrix itself is not changed.
+
+          -- ALGLIB routine --
+             15.12.2009
+             Bochkanov Sergey
+        *************************************************************************/
+        public static int ablasblocksize(double[,] a)
+        {
+            int result = 0;
+
+            result = 32;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Block size for complex subroutines.
+
+          -- ALGLIB routine --
+             15.12.2009
+             Bochkanov Sergey
+        *************************************************************************/
+        public static int ablascomplexblocksize(complex[,] a)
+        {
+            int result = 0;
+
+            result = 24;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Microblock size
+
+          -- ALGLIB routine --
+             15.12.2009
+             Bochkanov Sergey
+        *************************************************************************/
+        public static int ablasmicroblocksize()
+        {
+            int result = 0;
+
+            result = 8;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Cache-oblivous complex "copy-and-transpose"
+
+        Input parameters:
+            M   -   number of rows
+            N   -   number of columns
+            A   -   source matrix, MxN submatrix is copied and transposed
+            IA  -   submatrix offset (row index)
+            JA  -   submatrix offset (column index)
+            B   -   destination matrix, must be large enough to store result
+            IB  -   submatrix offset (row index)
+            JB  -   submatrix offset (column index)
+        *************************************************************************/
+        public static void cmatrixtranspose(int m,
+            int n,
+            complex[,] a,
+            int ia,
+            int ja,
+            ref complex[,] b,
+            int ib,
+            int jb)
+        {
+            int i = 0;
+            int s1 = 0;
+            int s2 = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            if( m<=2*ablascomplexblocksize(a) && n<=2*ablascomplexblocksize(a) )
+            {
+                
+                //
+                // base case
+                //
+                for(i=0; i<=m-1; i++)
+                {
+                    i1_ = (ja) - (ib);
+                    for(i_=ib; i_<=ib+n-1;i_++)
+                    {
+                        b[i_,jb+i] = a[ia+i,i_+i1_];
+                    }
+                }
+            }
+            else
+            {
+                
+                //
+                // Cache-oblivious recursion
+                //
+                if( m>n )
+                {
+                    ablascomplexsplitlength(a, m, ref s1, ref s2);
+                    cmatrixtranspose(s1, n, a, ia, ja, ref b, ib, jb);
+                    cmatrixtranspose(s2, n, a, ia+s1, ja, ref b, ib, jb+s1);
+                }
+                else
+                {
+                    ablascomplexsplitlength(a, n, ref s1, ref s2);
+                    cmatrixtranspose(m, s1, a, ia, ja, ref b, ib, jb);
+                    cmatrixtranspose(m, s2, a, ia, ja+s1, ref b, ib+s1, jb);
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Cache-oblivous real "copy-and-transpose"
+
+        Input parameters:
+            M   -   number of rows
+            N   -   number of columns
+            A   -   source matrix, MxN submatrix is copied and transposed
+            IA  -   submatrix offset (row index)
+            JA  -   submatrix offset (column index)
+            B   -   destination matrix, must be large enough to store result
+            IB  -   submatrix offset (row index)
+            JB  -   submatrix offset (column index)
+        *************************************************************************/
+        public static void rmatrixtranspose(int m,
+            int n,
+            double[,] a,
+            int ia,
+            int ja,
+            ref double[,] b,
+            int ib,
+            int jb)
+        {
+            int i = 0;
+            int s1 = 0;
+            int s2 = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            if( m<=2*ablasblocksize(a) && n<=2*ablasblocksize(a) )
+            {
+                
+                //
+                // base case
+                //
+                for(i=0; i<=m-1; i++)
+                {
+                    i1_ = (ja) - (ib);
+                    for(i_=ib; i_<=ib+n-1;i_++)
+                    {
+                        b[i_,jb+i] = a[ia+i,i_+i1_];
+                    }
+                }
+            }
+            else
+            {
+                
+                //
+                // Cache-oblivious recursion
+                //
+                if( m>n )
+                {
+                    ablassplitlength(a, m, ref s1, ref s2);
+                    rmatrixtranspose(s1, n, a, ia, ja, ref b, ib, jb);
+                    rmatrixtranspose(s2, n, a, ia+s1, ja, ref b, ib, jb+s1);
+                }
+                else
+                {
+                    ablassplitlength(a, n, ref s1, ref s2);
+                    rmatrixtranspose(m, s1, a, ia, ja, ref b, ib, jb);
+                    rmatrixtranspose(m, s2, a, ia, ja+s1, ref b, ib+s1, jb);
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Copy
+
+        Input parameters:
+            M   -   number of rows
+            N   -   number of columns
+            A   -   source matrix, MxN submatrix is copied and transposed
+            IA  -   submatrix offset (row index)
+            JA  -   submatrix offset (column index)
+            B   -   destination matrix, must be large enough to store result
+            IB  -   submatrix offset (row index)
+            JB  -   submatrix offset (column index)
+        *************************************************************************/
+        public static void cmatrixcopy(int m,
+            int n,
+            complex[,] a,
+            int ia,
+            int ja,
+            ref complex[,] b,
+            int ib,
+            int jb)
+        {
+            int i = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            if( m==0 || n==0 )
+            {
+                return;
+            }
+            for(i=0; i<=m-1; i++)
+            {
+                i1_ = (ja) - (jb);
+                for(i_=jb; i_<=jb+n-1;i_++)
+                {
+                    b[ib+i,i_] = a[ia+i,i_+i1_];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Copy
+
+        Input parameters:
+            M   -   number of rows
+            N   -   number of columns
+            A   -   source matrix, MxN submatrix is copied and transposed
+            IA  -   submatrix offset (row index)
+            JA  -   submatrix offset (column index)
+            B   -   destination matrix, must be large enough to store result
+            IB  -   submatrix offset (row index)
+            JB  -   submatrix offset (column index)
+        *************************************************************************/
+        public static void rmatrixcopy(int m,
+            int n,
+            double[,] a,
+            int ia,
+            int ja,
+            ref double[,] b,
+            int ib,
+            int jb)
+        {
+            int i = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            if( m==0 || n==0 )
+            {
+                return;
+            }
+            for(i=0; i<=m-1; i++)
+            {
+                i1_ = (ja) - (jb);
+                for(i_=jb; i_<=jb+n-1;i_++)
+                {
+                    b[ib+i,i_] = a[ia+i,i_+i1_];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Rank-1 correction: A := A + u*v'
+
+        INPUT PARAMETERS:
+            M   -   number of rows
+            N   -   number of columns
+            A   -   target matrix, MxN submatrix is updated
+            IA  -   submatrix offset (row index)
+            JA  -   submatrix offset (column index)
+            U   -   vector #1
+            IU  -   subvector offset
+            V   -   vector #2
+            IV  -   subvector offset
+        *************************************************************************/
+        public static void cmatrixrank1(int m,
+            int n,
+            ref complex[,] a,
+            int ia,
+            int ja,
+            ref complex[] u,
+            int iu,
+            ref complex[] v,
+            int iv)
+        {
+            int i = 0;
+            complex s = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            if( m==0 || n==0 )
+            {
+                return;
+            }
+            if( ablasf.cmatrixrank1f(m, n, ref a, ia, ja, ref u, iu, ref v, iv) )
+            {
+                return;
+            }
+            for(i=0; i<=m-1; i++)
+            {
+                s = u[iu+i];
+                i1_ = (iv) - (ja);
+                for(i_=ja; i_<=ja+n-1;i_++)
+                {
+                    a[ia+i,i_] = a[ia+i,i_] + s*v[i_+i1_];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Rank-1 correction: A := A + u*v'
+
+        INPUT PARAMETERS:
+            M   -   number of rows
+            N   -   number of columns
+            A   -   target matrix, MxN submatrix is updated
+            IA  -   submatrix offset (row index)
+            JA  -   submatrix offset (column index)
+            U   -   vector #1
+            IU  -   subvector offset
+            V   -   vector #2
+            IV  -   subvector offset
+        *************************************************************************/
+        public static void rmatrixrank1(int m,
+            int n,
+            ref double[,] a,
+            int ia,
+            int ja,
+            ref double[] u,
+            int iu,
+            ref double[] v,
+            int iv)
+        {
+            int i = 0;
+            double s = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            if( m==0 || n==0 )
+            {
+                return;
+            }
+            if( ablasf.rmatrixrank1f(m, n, ref a, ia, ja, ref u, iu, ref v, iv) )
+            {
+                return;
+            }
+            for(i=0; i<=m-1; i++)
+            {
+                s = u[iu+i];
+                i1_ = (iv) - (ja);
+                for(i_=ja; i_<=ja+n-1;i_++)
+                {
+                    a[ia+i,i_] = a[ia+i,i_] + s*v[i_+i1_];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Matrix-vector product: y := op(A)*x
+
+        INPUT PARAMETERS:
+            M   -   number of rows of op(A)
+                    M>=0
+            N   -   number of columns of op(A)
+                    N>=0
+            A   -   target matrix
+            IA  -   submatrix offset (row index)
+            JA  -   submatrix offset (column index)
+            OpA -   operation type:
+                    * OpA=0     =>  op(A) = A
+                    * OpA=1     =>  op(A) = A^T
+                    * OpA=2     =>  op(A) = A^H
+            X   -   input vector
+            IX  -   subvector offset
+            IY  -   subvector offset
+            Y   -   preallocated matrix, must be large enough to store result
+
+        OUTPUT PARAMETERS:
+            Y   -   vector which stores result
+
+        if M=0, then subroutine does nothing.
+        if N=0, Y is filled by zeros.
+
+
+          -- ALGLIB routine --
+
+             28.01.2010
+             Bochkanov Sergey
+        *************************************************************************/
+        public static void cmatrixmv(int m,
+            int n,
+            complex[,] a,
+            int ia,
+            int ja,
+            int opa,
+            complex[] x,
+            int ix,
+            ref complex[] y,
+            int iy)
+        {
+            int i = 0;
+            complex v = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            if( m==0 )
+            {
+                return;
+            }
+            if( n==0 )
+            {
+                for(i=0; i<=m-1; i++)
+                {
+                    y[iy+i] = 0;
+                }
+                return;
+            }
+            if( ablasf.cmatrixmvf(m, n, a, ia, ja, opa, x, ix, ref y, iy) )
+            {
+                return;
+            }
+            if( opa==0 )
+            {
+                
+                //
+                // y = A*x
+                //
+                for(i=0; i<=m-1; i++)
+                {
+                    i1_ = (ix)-(ja);
+                    v = 0.0;
+                    for(i_=ja; i_<=ja+n-1;i_++)
+                    {
+                        v += a[ia+i,i_]*x[i_+i1_];
+                    }
+                    y[iy+i] = v;
+                }
+                return;
+            }
+            if( opa==1 )
+            {
+                
+                //
+                // y = A^T*x
+                //
+                for(i=0; i<=m-1; i++)
+                {
+                    y[iy+i] = 0;
+                }
+                for(i=0; i<=n-1; i++)
+                {
+                    v = x[ix+i];
+                    i1_ = (ja) - (iy);
+                    for(i_=iy; i_<=iy+m-1;i_++)
+                    {
+                        y[i_] = y[i_] + v*a[ia+i,i_+i1_];
+                    }
+                }
+                return;
+            }
+            if( opa==2 )
+            {
+                
+                //
+                // y = A^H*x
+                //
+                for(i=0; i<=m-1; i++)
+                {
+                    y[iy+i] = 0;
+                }
+                for(i=0; i<=n-1; i++)
+                {
+                    v = x[ix+i];
+                    i1_ = (ja) - (iy);
+                    for(i_=iy; i_<=iy+m-1;i_++)
+                    {
+                        y[i_] = y[i_] + v*math.conj(a[ia+i,i_+i1_]);
+                    }
+                }
+                return;
+            }
+        }
+
+
+        /*************************************************************************
+        Matrix-vector product: y := op(A)*x
+
+        INPUT PARAMETERS:
+            M   -   number of rows of op(A)
+            N   -   number of columns of op(A)
+            A   -   target matrix
+            IA  -   submatrix offset (row index)
+            JA  -   submatrix offset (column index)
+            OpA -   operation type:
+                    * OpA=0     =>  op(A) = A
+                    * OpA=1     =>  op(A) = A^T
+            X   -   input vector
+            IX  -   subvector offset
+            IY  -   subvector offset
+            Y   -   preallocated matrix, must be large enough to store result
+
+        OUTPUT PARAMETERS:
+            Y   -   vector which stores result
+
+        if M=0, then subroutine does nothing.
+        if N=0, Y is filled by zeros.
+
+
+          -- ALGLIB routine --
+
+             28.01.2010
+             Bochkanov Sergey
+        *************************************************************************/
+        public static void rmatrixmv(int m,
+            int n,
+            double[,] a,
+            int ia,
+            int ja,
+            int opa,
+            double[] x,
+            int ix,
+            ref double[] y,
+            int iy)
+        {
+            int i = 0;
+            double v = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            if( m==0 )
+            {
+                return;
+            }
+            if( n==0 )
+            {
+                for(i=0; i<=m-1; i++)
+                {
+                    y[iy+i] = 0;
+                }
+                return;
+            }
+            if( ablasf.rmatrixmvf(m, n, a, ia, ja, opa, x, ix, ref y, iy) )
+            {
+                return;
+            }
+            if( opa==0 )
+            {
+                
+                //
+                // y = A*x
+                //
+                for(i=0; i<=m-1; i++)
+                {
+                    i1_ = (ix)-(ja);
+                    v = 0.0;
+                    for(i_=ja; i_<=ja+n-1;i_++)
+                    {
+                        v += a[ia+i,i_]*x[i_+i1_];
+                    }
+                    y[iy+i] = v;
+                }
+                return;
+            }
+            if( opa==1 )
+            {
+                
+                //
+                // y = A^T*x
+                //
+                for(i=0; i<=m-1; i++)
+                {
+                    y[iy+i] = 0;
+                }
+                for(i=0; i<=n-1; i++)
+                {
+                    v = x[ix+i];
+                    i1_ = (ja) - (iy);
+                    for(i_=iy; i_<=iy+m-1;i_++)
+                    {
+                        y[i_] = y[i_] + v*a[ia+i,i_+i1_];
+                    }
+                }
+                return;
+            }
+        }
+
+
+        /*************************************************************************
+        This subroutine calculates X*op(A^-1) where:
+        * X is MxN general matrix
+        * A is NxN upper/lower triangular/unitriangular matrix
+        * "op" may be identity transformation, transposition, conjugate transposition
+
+        Multiplication result replaces X.
+        Cache-oblivious algorithm is used.
+
+        INPUT PARAMETERS
+            N   -   matrix size, N>=0
+            M   -   matrix size, N>=0
+            A       -   matrix, actial matrix is stored in A[I1:I1+N-1,J1:J1+N-1]
+            I1      -   submatrix offset
+            J1      -   submatrix offset
+            IsUpper -   whether matrix is upper triangular
+            IsUnit  -   whether matrix is unitriangular
+            OpType  -   transformation type:
+                        * 0 - no transformation
+                        * 1 - transposition
+                        * 2 - conjugate transposition
+            X   -   matrix, actial matrix is stored in X[I2:I2+M-1,J2:J2+N-1]
+            I2  -   submatrix offset
+            J2  -   submatrix offset
+
+          -- ALGLIB routine --
+             15.12.2009
+             Bochkanov Sergey
+        *************************************************************************/
+        public static void cmatrixrighttrsm(int m,
+            int n,
+            complex[,] a,
+            int i1,
+            int j1,
+            bool isupper,
+            bool isunit,
+            int optype,
+            ref complex[,] x,
+            int i2,
+            int j2)
+        {
+            int s1 = 0;
+            int s2 = 0;
+            int bs = 0;
+
+            bs = ablascomplexblocksize(a);
+            if( m<=bs && n<=bs )
+            {
+                cmatrixrighttrsm2(m, n, a, i1, j1, isupper, isunit, optype, ref x, i2, j2);
+                return;
+            }
+            if( m>=n )
+            {
+                
+                //
+                // Split X: X*A = (X1 X2)^T*A
+                //
+                ablascomplexsplitlength(a, m, ref s1, ref s2);
+                cmatrixrighttrsm(s1, n, a, i1, j1, isupper, isunit, optype, ref x, i2, j2);
+                cmatrixrighttrsm(s2, n, a, i1, j1, isupper, isunit, optype, ref x, i2+s1, j2);
+            }
+            else
+            {
+                
+                //
+                // Split A:
+                //               (A1  A12)
+                // X*op(A) = X*op(       )
+                //               (     A2)
+                //
+                // Different variants depending on
+                // IsUpper/OpType combinations
+                //
+                ablascomplexsplitlength(a, n, ref s1, ref s2);
+                if( isupper && optype==0 )
+                {
+                    
+                    //
+                    //                  (A1  A12)-1
+                    // X*A^-1 = (X1 X2)*(       )
+                    //                  (     A2)
+                    //
+                    cmatrixrighttrsm(m, s1, a, i1, j1, isupper, isunit, optype, ref x, i2, j2);
+                    cmatrixgemm(m, s2, s1, -1.0, x, i2, j2, 0, a, i1, j1+s1, 0, 1.0, ref x, i2, j2+s1);
+                    cmatrixrighttrsm(m, s2, a, i1+s1, j1+s1, isupper, isunit, optype, ref x, i2, j2+s1);
+                    return;
+                }
+                if( isupper && optype!=0 )
+                {
+                    
+                    //
+                    //                  (A1'     )-1
+                    // X*A^-1 = (X1 X2)*(        )
+                    //                  (A12' A2')
+                    //
+                    cmatrixrighttrsm(m, s2, a, i1+s1, j1+s1, isupper, isunit, optype, ref x, i2, j2+s1);
+                    cmatrixgemm(m, s1, s2, -1.0, x, i2, j2+s1, 0, a, i1, j1+s1, optype, 1.0, ref x, i2, j2);
+                    cmatrixrighttrsm(m, s1, a, i1, j1, isupper, isunit, optype, ref x, i2, j2);
+                    return;
+                }
+                if( !isupper && optype==0 )
+                {
+                    
+                    //
+                    //                  (A1     )-1
+                    // X*A^-1 = (X1 X2)*(       )
+                    //                  (A21  A2)
+                    //
+                    cmatrixrighttrsm(m, s2, a, i1+s1, j1+s1, isupper, isunit, optype, ref x, i2, j2+s1);
+                    cmatrixgemm(m, s1, s2, -1.0, x, i2, j2+s1, 0, a, i1+s1, j1, 0, 1.0, ref x, i2, j2);
+                    cmatrixrighttrsm(m, s1, a, i1, j1, isupper, isunit, optype, ref x, i2, j2);
+                    return;
+                }
+                if( !isupper && optype!=0 )
+                {
+                    
+                    //
+                    //                  (A1' A21')-1
+                    // X*A^-1 = (X1 X2)*(        )
+                    //                  (     A2')
+                    //
+                    cmatrixrighttrsm(m, s1, a, i1, j1, isupper, isunit, optype, ref x, i2, j2);
+                    cmatrixgemm(m, s2, s1, -1.0, x, i2, j2, 0, a, i1+s1, j1, optype, 1.0, ref x, i2, j2+s1);
+                    cmatrixrighttrsm(m, s2, a, i1+s1, j1+s1, isupper, isunit, optype, ref x, i2, j2+s1);
+                    return;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This subroutine calculates op(A^-1)*X where:
+        * X is MxN general matrix
+        * A is MxM upper/lower triangular/unitriangular matrix
+        * "op" may be identity transformation, transposition, conjugate transposition
+
+        Multiplication result replaces X.
+        Cache-oblivious algorithm is used.
+
+        INPUT PARAMETERS
+            N   -   matrix size, N>=0
+            M   -   matrix size, N>=0
+            A       -   matrix, actial matrix is stored in A[I1:I1+M-1,J1:J1+M-1]
+            I1      -   submatrix offset
+            J1      -   submatrix offset
+            IsUpper -   whether matrix is upper triangular
+            IsUnit  -   whether matrix is unitriangular
+            OpType  -   transformation type:
+                        * 0 - no transformation
+                        * 1 - transposition
+                        * 2 - conjugate transposition
+            X   -   matrix, actial matrix is stored in X[I2:I2+M-1,J2:J2+N-1]
+            I2  -   submatrix offset
+            J2  -   submatrix offset
+
+          -- ALGLIB routine --
+             15.12.2009
+             Bochkanov Sergey
+        *************************************************************************/
+        public static void cmatrixlefttrsm(int m,
+            int n,
+            complex[,] a,
+            int i1,
+            int j1,
+            bool isupper,
+            bool isunit,
+            int optype,
+            ref complex[,] x,
+            int i2,
+            int j2)
+        {
+            int s1 = 0;
+            int s2 = 0;
+            int bs = 0;
+
+            bs = ablascomplexblocksize(a);
+            if( m<=bs && n<=bs )
+            {
+                cmatrixlefttrsm2(m, n, a, i1, j1, isupper, isunit, optype, ref x, i2, j2);
+                return;
+            }
+            if( n>=m )
+            {
+                
+                //
+                // Split X: op(A)^-1*X = op(A)^-1*(X1 X2)
+                //
+                ablascomplexsplitlength(x, n, ref s1, ref s2);
+                cmatrixlefttrsm(m, s1, a, i1, j1, isupper, isunit, optype, ref x, i2, j2);
+                cmatrixlefttrsm(m, s2, a, i1, j1, isupper, isunit, optype, ref x, i2, j2+s1);
+            }
+            else
+            {
+                
+                //
+                // Split A
+                //
+                ablascomplexsplitlength(a, m, ref s1, ref s2);
+                if( isupper && optype==0 )
+                {
+                    
+                    //
+                    //           (A1  A12)-1  ( X1 )
+                    // A^-1*X* = (       )   *(    )
+                    //           (     A2)    ( X2 )
+                    //
+                    cmatrixlefttrsm(s2, n, a, i1+s1, j1+s1, isupper, isunit, optype, ref x, i2+s1, j2);
+                    cmatrixgemm(s1, n, s2, -1.0, a, i1, j1+s1, 0, x, i2+s1, j2, 0, 1.0, ref x, i2, j2);
+                    cmatrixlefttrsm(s1, n, a, i1, j1, isupper, isunit, optype, ref x, i2, j2);
+                    return;
+                }
+                if( isupper && optype!=0 )
+                {
+                    
+                    //
+                    //          (A1'     )-1 ( X1 )
+                    // A^-1*X = (        )  *(    )
+                    //          (A12' A2')   ( X2 )
+                    //
+                    cmatrixlefttrsm(s1, n, a, i1, j1, isupper, isunit, optype, ref x, i2, j2);
+                    cmatrixgemm(s2, n, s1, -1.0, a, i1, j1+s1, optype, x, i2, j2, 0, 1.0, ref x, i2+s1, j2);
+                    cmatrixlefttrsm(s2, n, a, i1+s1, j1+s1, isupper, isunit, optype, ref x, i2+s1, j2);
+                    return;
+                }
+                if( !isupper && optype==0 )
+                {
+                    
+                    //
+                    //          (A1     )-1 ( X1 )
+                    // A^-1*X = (       )  *(    )
+                    //          (A21  A2)   ( X2 )
+                    //
+                    cmatrixlefttrsm(s1, n, a, i1, j1, isupper, isunit, optype, ref x, i2, j2);
+                    cmatrixgemm(s2, n, s1, -1.0, a, i1+s1, j1, 0, x, i2, j2, 0, 1.0, ref x, i2+s1, j2);
+                    cmatrixlefttrsm(s2, n, a, i1+s1, j1+s1, isupper, isunit, optype, ref x, i2+s1, j2);
+                    return;
+                }
+                if( !isupper && optype!=0 )
+                {
+                    
+                    //
+                    //          (A1' A21')-1 ( X1 )
+                    // A^-1*X = (        )  *(    )
+                    //          (     A2')   ( X2 )
+                    //
+                    cmatrixlefttrsm(s2, n, a, i1+s1, j1+s1, isupper, isunit, optype, ref x, i2+s1, j2);
+                    cmatrixgemm(s1, n, s2, -1.0, a, i1+s1, j1, optype, x, i2+s1, j2, 0, 1.0, ref x, i2, j2);
+                    cmatrixlefttrsm(s1, n, a, i1, j1, isupper, isunit, optype, ref x, i2, j2);
+                    return;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This subroutine calculates X*op(A^-1) where:
+        * X is MxN general matrix
+        * A is NxN upper/lower triangular/unitriangular matrix
+        * "op" may be identity transformation, transposition
+
+        Multiplication result replaces X.
+        Cache-oblivious algorithm is used.
+
+        INPUT PARAMETERS
+            N   -   matrix size, N>=0
+            M   -   matrix size, N>=0
+            A       -   matrix, actial matrix is stored in A[I1:I1+N-1,J1:J1+N-1]
+            I1      -   submatrix offset
+            J1      -   submatrix offset
+            IsUpper -   whether matrix is upper triangular
+            IsUnit  -   whether matrix is unitriangular
+            OpType  -   transformation type:
+                        * 0 - no transformation
+                        * 1 - transposition
+            X   -   matrix, actial matrix is stored in X[I2:I2+M-1,J2:J2+N-1]
+            I2  -   submatrix offset
+            J2  -   submatrix offset
+
+          -- ALGLIB routine --
+             15.12.2009
+             Bochkanov Sergey
+        *************************************************************************/
+        public static void rmatrixrighttrsm(int m,
+            int n,
+            double[,] a,
+            int i1,
+            int j1,
+            bool isupper,
+            bool isunit,
+            int optype,
+            ref double[,] x,
+            int i2,
+            int j2)
+        {
+            int s1 = 0;
+            int s2 = 0;
+            int bs = 0;
+
+            bs = ablasblocksize(a);
+            if( m<=bs && n<=bs )
+            {
+                rmatrixrighttrsm2(m, n, a, i1, j1, isupper, isunit, optype, ref x, i2, j2);
+                return;
+            }
+            if( m>=n )
+            {
+                
+                //
+                // Split X: X*A = (X1 X2)^T*A
+                //
+                ablassplitlength(a, m, ref s1, ref s2);
+                rmatrixrighttrsm(s1, n, a, i1, j1, isupper, isunit, optype, ref x, i2, j2);
+                rmatrixrighttrsm(s2, n, a, i1, j1, isupper, isunit, optype, ref x, i2+s1, j2);
+            }
+            else
+            {
+                
+                //
+                // Split A:
+                //               (A1  A12)
+                // X*op(A) = X*op(       )
+                //               (     A2)
+                //
+                // Different variants depending on
+                // IsUpper/OpType combinations
+                //
+                ablassplitlength(a, n, ref s1, ref s2);
+                if( isupper && optype==0 )
+                {
+                    
+                    //
+                    //                  (A1  A12)-1
+                    // X*A^-1 = (X1 X2)*(       )
+                    //                  (     A2)
+                    //
+                    rmatrixrighttrsm(m, s1, a, i1, j1, isupper, isunit, optype, ref x, i2, j2);
+                    rmatrixgemm(m, s2, s1, -1.0, x, i2, j2, 0, a, i1, j1+s1, 0, 1.0, ref x, i2, j2+s1);
+                    rmatrixrighttrsm(m, s2, a, i1+s1, j1+s1, isupper, isunit, optype, ref x, i2, j2+s1);
+                    return;
+                }
+                if( isupper && optype!=0 )
+                {
+                    
+                    //
+                    //                  (A1'     )-1
+                    // X*A^-1 = (X1 X2)*(        )
+                    //                  (A12' A2')
+                    //
+                    rmatrixrighttrsm(m, s2, a, i1+s1, j1+s1, isupper, isunit, optype, ref x, i2, j2+s1);
+                    rmatrixgemm(m, s1, s2, -1.0, x, i2, j2+s1, 0, a, i1, j1+s1, optype, 1.0, ref x, i2, j2);
+                    rmatrixrighttrsm(m, s1, a, i1, j1, isupper, isunit, optype, ref x, i2, j2);
+                    return;
+                }
+                if( !isupper && optype==0 )
+                {
+                    
+                    //
+                    //                  (A1     )-1
+                    // X*A^-1 = (X1 X2)*(       )
+                    //                  (A21  A2)
+                    //
+                    rmatrixrighttrsm(m, s2, a, i1+s1, j1+s1, isupper, isunit, optype, ref x, i2, j2+s1);
+                    rmatrixgemm(m, s1, s2, -1.0, x, i2, j2+s1, 0, a, i1+s1, j1, 0, 1.0, ref x, i2, j2);
+                    rmatrixrighttrsm(m, s1, a, i1, j1, isupper, isunit, optype, ref x, i2, j2);
+                    return;
+                }
+                if( !isupper && optype!=0 )
+                {
+                    
+                    //
+                    //                  (A1' A21')-1
+                    // X*A^-1 = (X1 X2)*(        )
+                    //                  (     A2')
+                    //
+                    rmatrixrighttrsm(m, s1, a, i1, j1, isupper, isunit, optype, ref x, i2, j2);
+                    rmatrixgemm(m, s2, s1, -1.0, x, i2, j2, 0, a, i1+s1, j1, optype, 1.0, ref x, i2, j2+s1);
+                    rmatrixrighttrsm(m, s2, a, i1+s1, j1+s1, isupper, isunit, optype, ref x, i2, j2+s1);
+                    return;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This subroutine calculates op(A^-1)*X where:
+        * X is MxN general matrix
+        * A is MxM upper/lower triangular/unitriangular matrix
+        * "op" may be identity transformation, transposition
+
+        Multiplication result replaces X.
+        Cache-oblivious algorithm is used.
+
+        INPUT PARAMETERS
+            N   -   matrix size, N>=0
+            M   -   matrix size, N>=0
+            A       -   matrix, actial matrix is stored in A[I1:I1+M-1,J1:J1+M-1]
+            I1      -   submatrix offset
+            J1      -   submatrix offset
+            IsUpper -   whether matrix is upper triangular
+            IsUnit  -   whether matrix is unitriangular
+            OpType  -   transformation type:
+                        * 0 - no transformation
+                        * 1 - transposition
+            X   -   matrix, actial matrix is stored in X[I2:I2+M-1,J2:J2+N-1]
+            I2  -   submatrix offset
+            J2  -   submatrix offset
+
+          -- ALGLIB routine --
+             15.12.2009
+             Bochkanov Sergey
+        *************************************************************************/
+        public static void rmatrixlefttrsm(int m,
+            int n,
+            double[,] a,
+            int i1,
+            int j1,
+            bool isupper,
+            bool isunit,
+            int optype,
+            ref double[,] x,
+            int i2,
+            int j2)
+        {
+            int s1 = 0;
+            int s2 = 0;
+            int bs = 0;
+
+            bs = ablasblocksize(a);
+            if( m<=bs && n<=bs )
+            {
+                rmatrixlefttrsm2(m, n, a, i1, j1, isupper, isunit, optype, ref x, i2, j2);
+                return;
+            }
+            if( n>=m )
+            {
+                
+                //
+                // Split X: op(A)^-1*X = op(A)^-1*(X1 X2)
+                //
+                ablassplitlength(x, n, ref s1, ref s2);
+                rmatrixlefttrsm(m, s1, a, i1, j1, isupper, isunit, optype, ref x, i2, j2);
+                rmatrixlefttrsm(m, s2, a, i1, j1, isupper, isunit, optype, ref x, i2, j2+s1);
+            }
+            else
+            {
+                
+                //
+                // Split A
+                //
+                ablassplitlength(a, m, ref s1, ref s2);
+                if( isupper && optype==0 )
+                {
+                    
+                    //
+                    //           (A1  A12)-1  ( X1 )
+                    // A^-1*X* = (       )   *(    )
+                    //           (     A2)    ( X2 )
+                    //
+                    rmatrixlefttrsm(s2, n, a, i1+s1, j1+s1, isupper, isunit, optype, ref x, i2+s1, j2);
+                    rmatrixgemm(s1, n, s2, -1.0, a, i1, j1+s1, 0, x, i2+s1, j2, 0, 1.0, ref x, i2, j2);
+                    rmatrixlefttrsm(s1, n, a, i1, j1, isupper, isunit, optype, ref x, i2, j2);
+                    return;
+                }
+                if( isupper && optype!=0 )
+                {
+                    
+                    //
+                    //          (A1'     )-1 ( X1 )
+                    // A^-1*X = (        )  *(    )
+                    //          (A12' A2')   ( X2 )
+                    //
+                    rmatrixlefttrsm(s1, n, a, i1, j1, isupper, isunit, optype, ref x, i2, j2);
+                    rmatrixgemm(s2, n, s1, -1.0, a, i1, j1+s1, optype, x, i2, j2, 0, 1.0, ref x, i2+s1, j2);
+                    rmatrixlefttrsm(s2, n, a, i1+s1, j1+s1, isupper, isunit, optype, ref x, i2+s1, j2);
+                    return;
+                }
+                if( !isupper && optype==0 )
+                {
+                    
+                    //
+                    //          (A1     )-1 ( X1 )
+                    // A^-1*X = (       )  *(    )
+                    //          (A21  A2)   ( X2 )
+                    //
+                    rmatrixlefttrsm(s1, n, a, i1, j1, isupper, isunit, optype, ref x, i2, j2);
+                    rmatrixgemm(s2, n, s1, -1.0, a, i1+s1, j1, 0, x, i2, j2, 0, 1.0, ref x, i2+s1, j2);
+                    rmatrixlefttrsm(s2, n, a, i1+s1, j1+s1, isupper, isunit, optype, ref x, i2+s1, j2);
+                    return;
+                }
+                if( !isupper && optype!=0 )
+                {
+                    
+                    //
+                    //          (A1' A21')-1 ( X1 )
+                    // A^-1*X = (        )  *(    )
+                    //          (     A2')   ( X2 )
+                    //
+                    rmatrixlefttrsm(s2, n, a, i1+s1, j1+s1, isupper, isunit, optype, ref x, i2+s1, j2);
+                    rmatrixgemm(s1, n, s2, -1.0, a, i1+s1, j1, optype, x, i2+s1, j2, 0, 1.0, ref x, i2, j2);
+                    rmatrixlefttrsm(s1, n, a, i1, j1, isupper, isunit, optype, ref x, i2, j2);
+                    return;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This subroutine calculates  C=alpha*A*A^H+beta*C  or  C=alpha*A^H*A+beta*C
+        where:
+        * C is NxN Hermitian matrix given by its upper/lower triangle
+        * A is NxK matrix when A*A^H is calculated, KxN matrix otherwise
+
+        Additional info:
+        * cache-oblivious algorithm is used.
+        * multiplication result replaces C. If Beta=0, C elements are not used in
+          calculations (not multiplied by zero - just not referenced)
+        * if Alpha=0, A is not used (not multiplied by zero - just not referenced)
+        * if both Beta and Alpha are zero, C is filled by zeros.
+
+        INPUT PARAMETERS
+            N       -   matrix size, N>=0
+            K       -   matrix size, K>=0
+            Alpha   -   coefficient
+            A       -   matrix
+            IA      -   submatrix offset
+            JA      -   submatrix offset
+            OpTypeA -   multiplication type:
+                        * 0 - A*A^H is calculated
+                        * 2 - A^H*A is calculated
+            Beta    -   coefficient
+            C       -   matrix
+            IC      -   submatrix offset
+            JC      -   submatrix offset
+            IsUpper -   whether C is upper triangular or lower triangular
+
+          -- ALGLIB routine --
+             16.12.2009
+             Bochkanov Sergey
+        *************************************************************************/
+        public static void cmatrixsyrk(int n,
+            int k,
+            double alpha,
+            complex[,] a,
+            int ia,
+            int ja,
+            int optypea,
+            double beta,
+            ref complex[,] c,
+            int ic,
+            int jc,
+            bool isupper)
+        {
+            int s1 = 0;
+            int s2 = 0;
+            int bs = 0;
+
+            bs = ablascomplexblocksize(a);
+            if( n<=bs && k<=bs )
+            {
+                cmatrixsyrk2(n, k, alpha, a, ia, ja, optypea, beta, ref c, ic, jc, isupper);
+                return;
+            }
+            if( k>=n )
+            {
+                
+                //
+                // Split K
+                //
+                ablascomplexsplitlength(a, k, ref s1, ref s2);
+                if( optypea==0 )
+                {
+                    cmatrixsyrk(n, s1, alpha, a, ia, ja, optypea, beta, ref c, ic, jc, isupper);
+                    cmatrixsyrk(n, s2, alpha, a, ia, ja+s1, optypea, 1.0, ref c, ic, jc, isupper);
+                }
+                else
+                {
+                    cmatrixsyrk(n, s1, alpha, a, ia, ja, optypea, beta, ref c, ic, jc, isupper);
+                    cmatrixsyrk(n, s2, alpha, a, ia+s1, ja, optypea, 1.0, ref c, ic, jc, isupper);
+                }
+            }
+            else
+            {
+                
+                //
+                // Split N
+                //
+                ablascomplexsplitlength(a, n, ref s1, ref s2);
+                if( optypea==0 && isupper )
+                {
+                    cmatrixsyrk(s1, k, alpha, a, ia, ja, optypea, beta, ref c, ic, jc, isupper);
+                    cmatrixgemm(s1, s2, k, alpha, a, ia, ja, 0, a, ia+s1, ja, 2, beta, ref c, ic, jc+s1);
+                    cmatrixsyrk(s2, k, alpha, a, ia+s1, ja, optypea, beta, ref c, ic+s1, jc+s1, isupper);
+                    return;
+                }
+                if( optypea==0 && !isupper )
+                {
+                    cmatrixsyrk(s1, k, alpha, a, ia, ja, optypea, beta, ref c, ic, jc, isupper);
+                    cmatrixgemm(s2, s1, k, alpha, a, ia+s1, ja, 0, a, ia, ja, 2, beta, ref c, ic+s1, jc);
+                    cmatrixsyrk(s2, k, alpha, a, ia+s1, ja, optypea, beta, ref c, ic+s1, jc+s1, isupper);
+                    return;
+                }
+                if( optypea!=0 && isupper )
+                {
+                    cmatrixsyrk(s1, k, alpha, a, ia, ja, optypea, beta, ref c, ic, jc, isupper);
+                    cmatrixgemm(s1, s2, k, alpha, a, ia, ja, 2, a, ia, ja+s1, 0, beta, ref c, ic, jc+s1);
+                    cmatrixsyrk(s2, k, alpha, a, ia, ja+s1, optypea, beta, ref c, ic+s1, jc+s1, isupper);
+                    return;
+                }
+                if( optypea!=0 && !isupper )
+                {
+                    cmatrixsyrk(s1, k, alpha, a, ia, ja, optypea, beta, ref c, ic, jc, isupper);
+                    cmatrixgemm(s2, s1, k, alpha, a, ia, ja+s1, 2, a, ia, ja, 0, beta, ref c, ic+s1, jc);
+                    cmatrixsyrk(s2, k, alpha, a, ia, ja+s1, optypea, beta, ref c, ic+s1, jc+s1, isupper);
+                    return;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This subroutine calculates  C=alpha*A*A^T+beta*C  or  C=alpha*A^T*A+beta*C
+        where:
+        * C is NxN symmetric matrix given by its upper/lower triangle
+        * A is NxK matrix when A*A^T is calculated, KxN matrix otherwise
+
+        Additional info:
+        * cache-oblivious algorithm is used.
+        * multiplication result replaces C. If Beta=0, C elements are not used in
+          calculations (not multiplied by zero - just not referenced)
+        * if Alpha=0, A is not used (not multiplied by zero - just not referenced)
+        * if both Beta and Alpha are zero, C is filled by zeros.
+
+        INPUT PARAMETERS
+            N       -   matrix size, N>=0
+            K       -   matrix size, K>=0
+            Alpha   -   coefficient
+            A       -   matrix
+            IA      -   submatrix offset
+            JA      -   submatrix offset
+            OpTypeA -   multiplication type:
+                        * 0 - A*A^T is calculated
+                        * 2 - A^T*A is calculated
+            Beta    -   coefficient
+            C       -   matrix
+            IC      -   submatrix offset
+            JC      -   submatrix offset
+            IsUpper -   whether C is upper triangular or lower triangular
+
+          -- ALGLIB routine --
+             16.12.2009
+             Bochkanov Sergey
+        *************************************************************************/
+        public static void rmatrixsyrk(int n,
+            int k,
+            double alpha,
+            double[,] a,
+            int ia,
+            int ja,
+            int optypea,
+            double beta,
+            ref double[,] c,
+            int ic,
+            int jc,
+            bool isupper)
+        {
+            int s1 = 0;
+            int s2 = 0;
+            int bs = 0;
+
+            bs = ablasblocksize(a);
+            if( n<=bs && k<=bs )
+            {
+                rmatrixsyrk2(n, k, alpha, a, ia, ja, optypea, beta, ref c, ic, jc, isupper);
+                return;
+            }
+            if( k>=n )
+            {
+                
+                //
+                // Split K
+                //
+                ablassplitlength(a, k, ref s1, ref s2);
+                if( optypea==0 )
+                {
+                    rmatrixsyrk(n, s1, alpha, a, ia, ja, optypea, beta, ref c, ic, jc, isupper);
+                    rmatrixsyrk(n, s2, alpha, a, ia, ja+s1, optypea, 1.0, ref c, ic, jc, isupper);
+                }
+                else
+                {
+                    rmatrixsyrk(n, s1, alpha, a, ia, ja, optypea, beta, ref c, ic, jc, isupper);
+                    rmatrixsyrk(n, s2, alpha, a, ia+s1, ja, optypea, 1.0, ref c, ic, jc, isupper);
+                }
+            }
+            else
+            {
+                
+                //
+                // Split N
+                //
+                ablassplitlength(a, n, ref s1, ref s2);
+                if( optypea==0 && isupper )
+                {
+                    rmatrixsyrk(s1, k, alpha, a, ia, ja, optypea, beta, ref c, ic, jc, isupper);
+                    rmatrixgemm(s1, s2, k, alpha, a, ia, ja, 0, a, ia+s1, ja, 1, beta, ref c, ic, jc+s1);
+                    rmatrixsyrk(s2, k, alpha, a, ia+s1, ja, optypea, beta, ref c, ic+s1, jc+s1, isupper);
+                    return;
+                }
+                if( optypea==0 && !isupper )
+                {
+                    rmatrixsyrk(s1, k, alpha, a, ia, ja, optypea, beta, ref c, ic, jc, isupper);
+                    rmatrixgemm(s2, s1, k, alpha, a, ia+s1, ja, 0, a, ia, ja, 1, beta, ref c, ic+s1, jc);
+                    rmatrixsyrk(s2, k, alpha, a, ia+s1, ja, optypea, beta, ref c, ic+s1, jc+s1, isupper);
+                    return;
+                }
+                if( optypea!=0 && isupper )
+                {
+                    rmatrixsyrk(s1, k, alpha, a, ia, ja, optypea, beta, ref c, ic, jc, isupper);
+                    rmatrixgemm(s1, s2, k, alpha, a, ia, ja, 1, a, ia, ja+s1, 0, beta, ref c, ic, jc+s1);
+                    rmatrixsyrk(s2, k, alpha, a, ia, ja+s1, optypea, beta, ref c, ic+s1, jc+s1, isupper);
+                    return;
+                }
+                if( optypea!=0 && !isupper )
+                {
+                    rmatrixsyrk(s1, k, alpha, a, ia, ja, optypea, beta, ref c, ic, jc, isupper);
+                    rmatrixgemm(s2, s1, k, alpha, a, ia, ja+s1, 1, a, ia, ja, 0, beta, ref c, ic+s1, jc);
+                    rmatrixsyrk(s2, k, alpha, a, ia, ja+s1, optypea, beta, ref c, ic+s1, jc+s1, isupper);
+                    return;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This subroutine calculates C = alpha*op1(A)*op2(B) +beta*C where:
+        * C is MxN general matrix
+        * op1(A) is MxK matrix
+        * op2(B) is KxN matrix
+        * "op" may be identity transformation, transposition, conjugate transposition
+
+        Additional info:
+        * cache-oblivious algorithm is used.
+        * multiplication result replaces C. If Beta=0, C elements are not used in
+          calculations (not multiplied by zero - just not referenced)
+        * if Alpha=0, A is not used (not multiplied by zero - just not referenced)
+        * if both Beta and Alpha are zero, C is filled by zeros.
+
+        INPUT PARAMETERS
+            M       -   matrix size, M>0
+            N       -   matrix size, N>0
+            K       -   matrix size, K>0
+            Alpha   -   coefficient
+            A       -   matrix
+            IA      -   submatrix offset
+            JA      -   submatrix offset
+            OpTypeA -   transformation type:
+                        * 0 - no transformation
+                        * 1 - transposition
+                        * 2 - conjugate transposition
+            B       -   matrix
+            IB      -   submatrix offset
+            JB      -   submatrix offset
+            OpTypeB -   transformation type:
+                        * 0 - no transformation
+                        * 1 - transposition
+                        * 2 - conjugate transposition
+            Beta    -   coefficient
+            C       -   matrix
+            IC      -   submatrix offset
+            JC      -   submatrix offset
+
+          -- ALGLIB routine --
+             16.12.2009
+             Bochkanov Sergey
+        *************************************************************************/
+        public static void cmatrixgemm(int m,
+            int n,
+            int k,
+            complex alpha,
+            complex[,] a,
+            int ia,
+            int ja,
+            int optypea,
+            complex[,] b,
+            int ib,
+            int jb,
+            int optypeb,
+            complex beta,
+            ref complex[,] c,
+            int ic,
+            int jc)
+        {
+            int s1 = 0;
+            int s2 = 0;
+            int bs = 0;
+
+            bs = ablascomplexblocksize(a);
+            if( (m<=bs && n<=bs) && k<=bs )
+            {
+                cmatrixgemmk(m, n, k, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, ref c, ic, jc);
+                return;
+            }
+            if( m>=n && m>=k )
+            {
+                
+                //
+                // A*B = (A1 A2)^T*B
+                //
+                ablascomplexsplitlength(a, m, ref s1, ref s2);
+                cmatrixgemm(s1, n, k, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, ref c, ic, jc);
+                if( optypea==0 )
+                {
+                    cmatrixgemm(s2, n, k, alpha, a, ia+s1, ja, optypea, b, ib, jb, optypeb, beta, ref c, ic+s1, jc);
+                }
+                else
+                {
+                    cmatrixgemm(s2, n, k, alpha, a, ia, ja+s1, optypea, b, ib, jb, optypeb, beta, ref c, ic+s1, jc);
+                }
+                return;
+            }
+            if( n>=m && n>=k )
+            {
+                
+                //
+                // A*B = A*(B1 B2)
+                //
+                ablascomplexsplitlength(a, n, ref s1, ref s2);
+                if( optypeb==0 )
+                {
+                    cmatrixgemm(m, s1, k, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, ref c, ic, jc);
+                    cmatrixgemm(m, s2, k, alpha, a, ia, ja, optypea, b, ib, jb+s1, optypeb, beta, ref c, ic, jc+s1);
+                }
+                else
+                {
+                    cmatrixgemm(m, s1, k, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, ref c, ic, jc);
+                    cmatrixgemm(m, s2, k, alpha, a, ia, ja, optypea, b, ib+s1, jb, optypeb, beta, ref c, ic, jc+s1);
+                }
+                return;
+            }
+            if( k>=m && k>=n )
+            {
+                
+                //
+                // A*B = (A1 A2)*(B1 B2)^T
+                //
+                ablascomplexsplitlength(a, k, ref s1, ref s2);
+                if( optypea==0 && optypeb==0 )
+                {
+                    cmatrixgemm(m, n, s1, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, ref c, ic, jc);
+                    cmatrixgemm(m, n, s2, alpha, a, ia, ja+s1, optypea, b, ib+s1, jb, optypeb, 1.0, ref c, ic, jc);
+                }
+                if( optypea==0 && optypeb!=0 )
+                {
+                    cmatrixgemm(m, n, s1, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, ref c, ic, jc);
+                    cmatrixgemm(m, n, s2, alpha, a, ia, ja+s1, optypea, b, ib, jb+s1, optypeb, 1.0, ref c, ic, jc);
+                }
+                if( optypea!=0 && optypeb==0 )
+                {
+                    cmatrixgemm(m, n, s1, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, ref c, ic, jc);
+                    cmatrixgemm(m, n, s2, alpha, a, ia+s1, ja, optypea, b, ib+s1, jb, optypeb, 1.0, ref c, ic, jc);
+                }
+                if( optypea!=0 && optypeb!=0 )
+                {
+                    cmatrixgemm(m, n, s1, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, ref c, ic, jc);
+                    cmatrixgemm(m, n, s2, alpha, a, ia+s1, ja, optypea, b, ib, jb+s1, optypeb, 1.0, ref c, ic, jc);
+                }
+                return;
+            }
+        }
+
+
+        /*************************************************************************
+        This subroutine calculates C = alpha*op1(A)*op2(B) +beta*C where:
+        * C is MxN general matrix
+        * op1(A) is MxK matrix
+        * op2(B) is KxN matrix
+        * "op" may be identity transformation, transposition
+
+        Additional info:
+        * cache-oblivious algorithm is used.
+        * multiplication result replaces C. If Beta=0, C elements are not used in
+          calculations (not multiplied by zero - just not referenced)
+        * if Alpha=0, A is not used (not multiplied by zero - just not referenced)
+        * if both Beta and Alpha are zero, C is filled by zeros.
+
+        INPUT PARAMETERS
+            M       -   matrix size, M>0
+            N       -   matrix size, N>0
+            K       -   matrix size, K>0
+            Alpha   -   coefficient
+            A       -   matrix
+            IA      -   submatrix offset
+            JA      -   submatrix offset
+            OpTypeA -   transformation type:
+                        * 0 - no transformation
+                        * 1 - transposition
+            B       -   matrix
+            IB      -   submatrix offset
+            JB      -   submatrix offset
+            OpTypeB -   transformation type:
+                        * 0 - no transformation
+                        * 1 - transposition
+            Beta    -   coefficient
+            C       -   matrix
+            IC      -   submatrix offset
+            JC      -   submatrix offset
+
+          -- ALGLIB routine --
+             16.12.2009
+             Bochkanov Sergey
+        *************************************************************************/
+        public static void rmatrixgemm(int m,
+            int n,
+            int k,
+            double alpha,
+            double[,] a,
+            int ia,
+            int ja,
+            int optypea,
+            double[,] b,
+            int ib,
+            int jb,
+            int optypeb,
+            double beta,
+            ref double[,] c,
+            int ic,
+            int jc)
+        {
+            int s1 = 0;
+            int s2 = 0;
+            int bs = 0;
+
+            bs = ablasblocksize(a);
+            if( (m<=bs && n<=bs) && k<=bs )
+            {
+                rmatrixgemmk(m, n, k, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, ref c, ic, jc);
+                return;
+            }
+            if( m>=n && m>=k )
+            {
+                
+                //
+                // A*B = (A1 A2)^T*B
+                //
+                ablassplitlength(a, m, ref s1, ref s2);
+                if( optypea==0 )
+                {
+                    rmatrixgemm(s1, n, k, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, ref c, ic, jc);
+                    rmatrixgemm(s2, n, k, alpha, a, ia+s1, ja, optypea, b, ib, jb, optypeb, beta, ref c, ic+s1, jc);
+                }
+                else
+                {
+                    rmatrixgemm(s1, n, k, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, ref c, ic, jc);
+                    rmatrixgemm(s2, n, k, alpha, a, ia, ja+s1, optypea, b, ib, jb, optypeb, beta, ref c, ic+s1, jc);
+                }
+                return;
+            }
+            if( n>=m && n>=k )
+            {
+                
+                //
+                // A*B = A*(B1 B2)
+                //
+                ablassplitlength(a, n, ref s1, ref s2);
+                if( optypeb==0 )
+                {
+                    rmatrixgemm(m, s1, k, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, ref c, ic, jc);
+                    rmatrixgemm(m, s2, k, alpha, a, ia, ja, optypea, b, ib, jb+s1, optypeb, beta, ref c, ic, jc+s1);
+                }
+                else
+                {
+                    rmatrixgemm(m, s1, k, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, ref c, ic, jc);
+                    rmatrixgemm(m, s2, k, alpha, a, ia, ja, optypea, b, ib+s1, jb, optypeb, beta, ref c, ic, jc+s1);
+                }
+                return;
+            }
+            if( k>=m && k>=n )
+            {
+                
+                //
+                // A*B = (A1 A2)*(B1 B2)^T
+                //
+                ablassplitlength(a, k, ref s1, ref s2);
+                if( optypea==0 && optypeb==0 )
+                {
+                    rmatrixgemm(m, n, s1, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, ref c, ic, jc);
+                    rmatrixgemm(m, n, s2, alpha, a, ia, ja+s1, optypea, b, ib+s1, jb, optypeb, 1.0, ref c, ic, jc);
+                }
+                if( optypea==0 && optypeb!=0 )
+                {
+                    rmatrixgemm(m, n, s1, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, ref c, ic, jc);
+                    rmatrixgemm(m, n, s2, alpha, a, ia, ja+s1, optypea, b, ib, jb+s1, optypeb, 1.0, ref c, ic, jc);
+                }
+                if( optypea!=0 && optypeb==0 )
+                {
+                    rmatrixgemm(m, n, s1, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, ref c, ic, jc);
+                    rmatrixgemm(m, n, s2, alpha, a, ia+s1, ja, optypea, b, ib+s1, jb, optypeb, 1.0, ref c, ic, jc);
+                }
+                if( optypea!=0 && optypeb!=0 )
+                {
+                    rmatrixgemm(m, n, s1, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, ref c, ic, jc);
+                    rmatrixgemm(m, n, s2, alpha, a, ia+s1, ja, optypea, b, ib, jb+s1, optypeb, 1.0, ref c, ic, jc);
+                }
+                return;
+            }
+        }
+
+
+        /*************************************************************************
+        Complex ABLASSplitLength
+
+          -- ALGLIB routine --
+             15.12.2009
+             Bochkanov Sergey
+        *************************************************************************/
+        private static void ablasinternalsplitlength(int n,
+            int nb,
+            ref int n1,
+            ref int n2)
+        {
+            int r = 0;
+
+            n1 = 0;
+            n2 = 0;
+
+            if( n<=nb )
+            {
+                
+                //
+                // Block size, no further splitting
+                //
+                n1 = n;
+                n2 = 0;
+            }
+            else
+            {
+                
+                //
+                // Greater than block size
+                //
+                if( n%nb!=0 )
+                {
+                    
+                    //
+                    // Split remainder
+                    //
+                    n2 = n%nb;
+                    n1 = n-n2;
+                }
+                else
+                {
+                    
+                    //
+                    // Split on block boundaries
+                    //
+                    n2 = n/2;
+                    n1 = n-n2;
+                    if( n1%nb==0 )
+                    {
+                        return;
+                    }
+                    r = nb-n1%nb;
+                    n1 = n1+r;
+                    n2 = n2-r;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Level 2 variant of CMatrixRightTRSM
+        *************************************************************************/
+        private static void cmatrixrighttrsm2(int m,
+            int n,
+            complex[,] a,
+            int i1,
+            int j1,
+            bool isupper,
+            bool isunit,
+            int optype,
+            ref complex[,] x,
+            int i2,
+            int j2)
+        {
+            int i = 0;
+            int j = 0;
+            complex vc = 0;
+            complex vd = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            
+            //
+            // Special case
+            //
+            if( n*m==0 )
+            {
+                return;
+            }
+            
+            //
+            // Try to call fast TRSM
+            //
+            if( ablasf.cmatrixrighttrsmf(m, n, a, i1, j1, isupper, isunit, optype, ref x, i2, j2) )
+            {
+                return;
+            }
+            
+            //
+            // General case
+            //
+            if( isupper )
+            {
+                
+                //
+                // Upper triangular matrix
+                //
+                if( optype==0 )
+                {
+                    
+                    //
+                    // X*A^(-1)
+                    //
+                    for(i=0; i<=m-1; i++)
+                    {
+                        for(j=0; j<=n-1; j++)
+                        {
+                            if( isunit )
+                            {
+                                vd = 1;
+                            }
+                            else
+                            {
+                                vd = a[i1+j,j1+j];
+                            }
+                            x[i2+i,j2+j] = x[i2+i,j2+j]/vd;
+                            if( j<n-1 )
+                            {
+                                vc = x[i2+i,j2+j];
+                                i1_ = (j1+j+1) - (j2+j+1);
+                                for(i_=j2+j+1; i_<=j2+n-1;i_++)
+                                {
+                                    x[i2+i,i_] = x[i2+i,i_] - vc*a[i1+j,i_+i1_];
+                                }
+                            }
+                        }
+                    }
+                    return;
+                }
+                if( optype==1 )
+                {
+                    
+                    //
+                    // X*A^(-T)
+                    //
+                    for(i=0; i<=m-1; i++)
+                    {
+                        for(j=n-1; j>=0; j--)
+                        {
+                            vc = 0;
+                            vd = 1;
+                            if( j<n-1 )
+                            {
+                                i1_ = (j1+j+1)-(j2+j+1);
+                                vc = 0.0;
+                                for(i_=j2+j+1; i_<=j2+n-1;i_++)
+                                {
+                                    vc += x[i2+i,i_]*a[i1+j,i_+i1_];
+                                }
+                            }
+                            if( !isunit )
+                            {
+                                vd = a[i1+j,j1+j];
+                            }
+                            x[i2+i,j2+j] = (x[i2+i,j2+j]-vc)/vd;
+                        }
+                    }
+                    return;
+                }
+                if( optype==2 )
+                {
+                    
+                    //
+                    // X*A^(-H)
+                    //
+                    for(i=0; i<=m-1; i++)
+                    {
+                        for(j=n-1; j>=0; j--)
+                        {
+                            vc = 0;
+                            vd = 1;
+                            if( j<n-1 )
+                            {
+                                i1_ = (j1+j+1)-(j2+j+1);
+                                vc = 0.0;
+                                for(i_=j2+j+1; i_<=j2+n-1;i_++)
+                                {
+                                    vc += x[i2+i,i_]*math.conj(a[i1+j,i_+i1_]);
+                                }
+                            }
+                            if( !isunit )
+                            {
+                                vd = math.conj(a[i1+j,j1+j]);
+                            }
+                            x[i2+i,j2+j] = (x[i2+i,j2+j]-vc)/vd;
+                        }
+                    }
+                    return;
+                }
+            }
+            else
+            {
+                
+                //
+                // Lower triangular matrix
+                //
+                if( optype==0 )
+                {
+                    
+                    //
+                    // X*A^(-1)
+                    //
+                    for(i=0; i<=m-1; i++)
+                    {
+                        for(j=n-1; j>=0; j--)
+                        {
+                            if( isunit )
+                            {
+                                vd = 1;
+                            }
+                            else
+                            {
+                                vd = a[i1+j,j1+j];
+                            }
+                            x[i2+i,j2+j] = x[i2+i,j2+j]/vd;
+                            if( j>0 )
+                            {
+                                vc = x[i2+i,j2+j];
+                                i1_ = (j1) - (j2);
+                                for(i_=j2; i_<=j2+j-1;i_++)
+                                {
+                                    x[i2+i,i_] = x[i2+i,i_] - vc*a[i1+j,i_+i1_];
+                                }
+                            }
+                        }
+                    }
+                    return;
+                }
+                if( optype==1 )
+                {
+                    
+                    //
+                    // X*A^(-T)
+                    //
+                    for(i=0; i<=m-1; i++)
+                    {
+                        for(j=0; j<=n-1; j++)
+                        {
+                            vc = 0;
+                            vd = 1;
+                            if( j>0 )
+                            {
+                                i1_ = (j1)-(j2);
+                                vc = 0.0;
+                                for(i_=j2; i_<=j2+j-1;i_++)
+                                {
+                                    vc += x[i2+i,i_]*a[i1+j,i_+i1_];
+                                }
+                            }
+                            if( !isunit )
+                            {
+                                vd = a[i1+j,j1+j];
+                            }
+                            x[i2+i,j2+j] = (x[i2+i,j2+j]-vc)/vd;
+                        }
+                    }
+                    return;
+                }
+                if( optype==2 )
+                {
+                    
+                    //
+                    // X*A^(-H)
+                    //
+                    for(i=0; i<=m-1; i++)
+                    {
+                        for(j=0; j<=n-1; j++)
+                        {
+                            vc = 0;
+                            vd = 1;
+                            if( j>0 )
+                            {
+                                i1_ = (j1)-(j2);
+                                vc = 0.0;
+                                for(i_=j2; i_<=j2+j-1;i_++)
+                                {
+                                    vc += x[i2+i,i_]*math.conj(a[i1+j,i_+i1_]);
+                                }
+                            }
+                            if( !isunit )
+                            {
+                                vd = math.conj(a[i1+j,j1+j]);
+                            }
+                            x[i2+i,j2+j] = (x[i2+i,j2+j]-vc)/vd;
+                        }
+                    }
+                    return;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Level-2 subroutine
+        *************************************************************************/
+        private static void cmatrixlefttrsm2(int m,
+            int n,
+            complex[,] a,
+            int i1,
+            int j1,
+            bool isupper,
+            bool isunit,
+            int optype,
+            ref complex[,] x,
+            int i2,
+            int j2)
+        {
+            int i = 0;
+            int j = 0;
+            complex vc = 0;
+            complex vd = 0;
+            int i_ = 0;
+
+            
+            //
+            // Special case
+            //
+            if( n*m==0 )
+            {
+                return;
+            }
+            
+            //
+            // Try to call fast TRSM
+            //
+            if( ablasf.cmatrixlefttrsmf(m, n, a, i1, j1, isupper, isunit, optype, ref x, i2, j2) )
+            {
+                return;
+            }
+            
+            //
+            // General case
+            //
+            if( isupper )
+            {
+                
+                //
+                // Upper triangular matrix
+                //
+                if( optype==0 )
+                {
+                    
+                    //
+                    // A^(-1)*X
+                    //
+                    for(i=m-1; i>=0; i--)
+                    {
+                        for(j=i+1; j<=m-1; j++)
+                        {
+                            vc = a[i1+i,j1+j];
+                            for(i_=j2; i_<=j2+n-1;i_++)
+                            {
+                                x[i2+i,i_] = x[i2+i,i_] - vc*x[i2+j,i_];
+                            }
+                        }
+                        if( !isunit )
+                        {
+                            vd = 1/a[i1+i,j1+i];
+                            for(i_=j2; i_<=j2+n-1;i_++)
+                            {
+                                x[i2+i,i_] = vd*x[i2+i,i_];
+                            }
+                        }
+                    }
+                    return;
+                }
+                if( optype==1 )
+                {
+                    
+                    //
+                    // A^(-T)*X
+                    //
+                    for(i=0; i<=m-1; i++)
+                    {
+                        if( isunit )
+                        {
+                            vd = 1;
+                        }
+                        else
+                        {
+                            vd = 1/a[i1+i,j1+i];
+                        }
+                        for(i_=j2; i_<=j2+n-1;i_++)
+                        {
+                            x[i2+i,i_] = vd*x[i2+i,i_];
+                        }
+                        for(j=i+1; j<=m-1; j++)
+                        {
+                            vc = a[i1+i,j1+j];
+                            for(i_=j2; i_<=j2+n-1;i_++)
+                            {
+                                x[i2+j,i_] = x[i2+j,i_] - vc*x[i2+i,i_];
+                            }
+                        }
+                    }
+                    return;
+                }
+                if( optype==2 )
+                {
+                    
+                    //
+                    // A^(-H)*X
+                    //
+                    for(i=0; i<=m-1; i++)
+                    {
+                        if( isunit )
+                        {
+                            vd = 1;
+                        }
+                        else
+                        {
+                            vd = 1/math.conj(a[i1+i,j1+i]);
+                        }
+                        for(i_=j2; i_<=j2+n-1;i_++)
+                        {
+                            x[i2+i,i_] = vd*x[i2+i,i_];
+                        }
+                        for(j=i+1; j<=m-1; j++)
+                        {
+                            vc = math.conj(a[i1+i,j1+j]);
+                            for(i_=j2; i_<=j2+n-1;i_++)
+                            {
+                                x[i2+j,i_] = x[i2+j,i_] - vc*x[i2+i,i_];
+                            }
+                        }
+                    }
+                    return;
+                }
+            }
+            else
+            {
+                
+                //
+                // Lower triangular matrix
+                //
+                if( optype==0 )
+                {
+                    
+                    //
+                    // A^(-1)*X
+                    //
+                    for(i=0; i<=m-1; i++)
+                    {
+                        for(j=0; j<=i-1; j++)
+                        {
+                            vc = a[i1+i,j1+j];
+                            for(i_=j2; i_<=j2+n-1;i_++)
+                            {
+                                x[i2+i,i_] = x[i2+i,i_] - vc*x[i2+j,i_];
+                            }
+                        }
+                        if( isunit )
+                        {
+                            vd = 1;
+                        }
+                        else
+                        {
+                            vd = 1/a[i1+j,j1+j];
+                        }
+                        for(i_=j2; i_<=j2+n-1;i_++)
+                        {
+                            x[i2+i,i_] = vd*x[i2+i,i_];
+                        }
+                    }
+                    return;
+                }
+                if( optype==1 )
+                {
+                    
+                    //
+                    // A^(-T)*X
+                    //
+                    for(i=m-1; i>=0; i--)
+                    {
+                        if( isunit )
+                        {
+                            vd = 1;
+                        }
+                        else
+                        {
+                            vd = 1/a[i1+i,j1+i];
+                        }
+                        for(i_=j2; i_<=j2+n-1;i_++)
+                        {
+                            x[i2+i,i_] = vd*x[i2+i,i_];
+                        }
+                        for(j=i-1; j>=0; j--)
+                        {
+                            vc = a[i1+i,j1+j];
+                            for(i_=j2; i_<=j2+n-1;i_++)
+                            {
+                                x[i2+j,i_] = x[i2+j,i_] - vc*x[i2+i,i_];
+                            }
+                        }
+                    }
+                    return;
+                }
+                if( optype==2 )
+                {
+                    
+                    //
+                    // A^(-H)*X
+                    //
+                    for(i=m-1; i>=0; i--)
+                    {
+                        if( isunit )
+                        {
+                            vd = 1;
+                        }
+                        else
+                        {
+                            vd = 1/math.conj(a[i1+i,j1+i]);
+                        }
+                        for(i_=j2; i_<=j2+n-1;i_++)
+                        {
+                            x[i2+i,i_] = vd*x[i2+i,i_];
+                        }
+                        for(j=i-1; j>=0; j--)
+                        {
+                            vc = math.conj(a[i1+i,j1+j]);
+                            for(i_=j2; i_<=j2+n-1;i_++)
+                            {
+                                x[i2+j,i_] = x[i2+j,i_] - vc*x[i2+i,i_];
+                            }
+                        }
+                    }
+                    return;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Level 2 subroutine
+
+          -- ALGLIB routine --
+             15.12.2009
+             Bochkanov Sergey
+        *************************************************************************/
+        private static void rmatrixrighttrsm2(int m,
+            int n,
+            double[,] a,
+            int i1,
+            int j1,
+            bool isupper,
+            bool isunit,
+            int optype,
+            ref double[,] x,
+            int i2,
+            int j2)
+        {
+            int i = 0;
+            int j = 0;
+            double vr = 0;
+            double vd = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            
+            //
+            // Special case
+            //
+            if( n*m==0 )
+            {
+                return;
+            }
+            
+            //
+            // Try to use "fast" code
+            //
+            if( ablasf.rmatrixrighttrsmf(m, n, a, i1, j1, isupper, isunit, optype, ref x, i2, j2) )
+            {
+                return;
+            }
+            
+            //
+            // General case
+            //
+            if( isupper )
+            {
+                
+                //
+                // Upper triangular matrix
+                //
+                if( optype==0 )
+                {
+                    
+                    //
+                    // X*A^(-1)
+                    //
+                    for(i=0; i<=m-1; i++)
+                    {
+                        for(j=0; j<=n-1; j++)
+                        {
+                            if( isunit )
+                            {
+                                vd = 1;
+                            }
+                            else
+                            {
+                                vd = a[i1+j,j1+j];
+                            }
+                            x[i2+i,j2+j] = x[i2+i,j2+j]/vd;
+                            if( j<n-1 )
+                            {
+                                vr = x[i2+i,j2+j];
+                                i1_ = (j1+j+1) - (j2+j+1);
+                                for(i_=j2+j+1; i_<=j2+n-1;i_++)
+                                {
+                                    x[i2+i,i_] = x[i2+i,i_] - vr*a[i1+j,i_+i1_];
+                                }
+                            }
+                        }
+                    }
+                    return;
+                }
+                if( optype==1 )
+                {
+                    
+                    //
+                    // X*A^(-T)
+                    //
+                    for(i=0; i<=m-1; i++)
+                    {
+                        for(j=n-1; j>=0; j--)
+                        {
+                            vr = 0;
+                            vd = 1;
+                            if( j<n-1 )
+                            {
+                                i1_ = (j1+j+1)-(j2+j+1);
+                                vr = 0.0;
+                                for(i_=j2+j+1; i_<=j2+n-1;i_++)
+                                {
+                                    vr += x[i2+i,i_]*a[i1+j,i_+i1_];
+                                }
+                            }
+                            if( !isunit )
+                            {
+                                vd = a[i1+j,j1+j];
+                            }
+                            x[i2+i,j2+j] = (x[i2+i,j2+j]-vr)/vd;
+                        }
+                    }
+                    return;
+                }
+            }
+            else
+            {
+                
+                //
+                // Lower triangular matrix
+                //
+                if( optype==0 )
+                {
+                    
+                    //
+                    // X*A^(-1)
+                    //
+                    for(i=0; i<=m-1; i++)
+                    {
+                        for(j=n-1; j>=0; j--)
+                        {
+                            if( isunit )
+                            {
+                                vd = 1;
+                            }
+                            else
+                            {
+                                vd = a[i1+j,j1+j];
+                            }
+                            x[i2+i,j2+j] = x[i2+i,j2+j]/vd;
+                            if( j>0 )
+                            {
+                                vr = x[i2+i,j2+j];
+                                i1_ = (j1) - (j2);
+                                for(i_=j2; i_<=j2+j-1;i_++)
+                                {
+                                    x[i2+i,i_] = x[i2+i,i_] - vr*a[i1+j,i_+i1_];
+                                }
+                            }
+                        }
+                    }
+                    return;
+                }
+                if( optype==1 )
+                {
+                    
+                    //
+                    // X*A^(-T)
+                    //
+                    for(i=0; i<=m-1; i++)
+                    {
+                        for(j=0; j<=n-1; j++)
+                        {
+                            vr = 0;
+                            vd = 1;
+                            if( j>0 )
+                            {
+                                i1_ = (j1)-(j2);
+                                vr = 0.0;
+                                for(i_=j2; i_<=j2+j-1;i_++)
+                                {
+                                    vr += x[i2+i,i_]*a[i1+j,i_+i1_];
+                                }
+                            }
+                            if( !isunit )
+                            {
+                                vd = a[i1+j,j1+j];
+                            }
+                            x[i2+i,j2+j] = (x[i2+i,j2+j]-vr)/vd;
+                        }
+                    }
+                    return;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Level 2 subroutine
+        *************************************************************************/
+        private static void rmatrixlefttrsm2(int m,
+            int n,
+            double[,] a,
+            int i1,
+            int j1,
+            bool isupper,
+            bool isunit,
+            int optype,
+            ref double[,] x,
+            int i2,
+            int j2)
+        {
+            int i = 0;
+            int j = 0;
+            double vr = 0;
+            double vd = 0;
+            int i_ = 0;
+
+            
+            //
+            // Special case
+            //
+            if( n*m==0 )
+            {
+                return;
+            }
+            
+            //
+            // Try fast code
+            //
+            if( ablasf.rmatrixlefttrsmf(m, n, a, i1, j1, isupper, isunit, optype, ref x, i2, j2) )
+            {
+                return;
+            }
+            
+            //
+            // General case
+            //
+            if( isupper )
+            {
+                
+                //
+                // Upper triangular matrix
+                //
+                if( optype==0 )
+                {
+                    
+                    //
+                    // A^(-1)*X
+                    //
+                    for(i=m-1; i>=0; i--)
+                    {
+                        for(j=i+1; j<=m-1; j++)
+                        {
+                            vr = a[i1+i,j1+j];
+                            for(i_=j2; i_<=j2+n-1;i_++)
+                            {
+                                x[i2+i,i_] = x[i2+i,i_] - vr*x[i2+j,i_];
+                            }
+                        }
+                        if( !isunit )
+                        {
+                            vd = 1/a[i1+i,j1+i];
+                            for(i_=j2; i_<=j2+n-1;i_++)
+                            {
+                                x[i2+i,i_] = vd*x[i2+i,i_];
+                            }
+                        }
+                    }
+                    return;
+                }
+                if( optype==1 )
+                {
+                    
+                    //
+                    // A^(-T)*X
+                    //
+                    for(i=0; i<=m-1; i++)
+                    {
+                        if( isunit )
+                        {
+                            vd = 1;
+                        }
+                        else
+                        {
+                            vd = 1/a[i1+i,j1+i];
+                        }
+                        for(i_=j2; i_<=j2+n-1;i_++)
+                        {
+                            x[i2+i,i_] = vd*x[i2+i,i_];
+                        }
+                        for(j=i+1; j<=m-1; j++)
+                        {
+                            vr = a[i1+i,j1+j];
+                            for(i_=j2; i_<=j2+n-1;i_++)
+                            {
+                                x[i2+j,i_] = x[i2+j,i_] - vr*x[i2+i,i_];
+                            }
+                        }
+                    }
+                    return;
+                }
+            }
+            else
+            {
+                
+                //
+                // Lower triangular matrix
+                //
+                if( optype==0 )
+                {
+                    
+                    //
+                    // A^(-1)*X
+                    //
+                    for(i=0; i<=m-1; i++)
+                    {
+                        for(j=0; j<=i-1; j++)
+                        {
+                            vr = a[i1+i,j1+j];
+                            for(i_=j2; i_<=j2+n-1;i_++)
+                            {
+                                x[i2+i,i_] = x[i2+i,i_] - vr*x[i2+j,i_];
+                            }
+                        }
+                        if( isunit )
+                        {
+                            vd = 1;
+                        }
+                        else
+                        {
+                            vd = 1/a[i1+j,j1+j];
+                        }
+                        for(i_=j2; i_<=j2+n-1;i_++)
+                        {
+                            x[i2+i,i_] = vd*x[i2+i,i_];
+                        }
+                    }
+                    return;
+                }
+                if( optype==1 )
+                {
+                    
+                    //
+                    // A^(-T)*X
+                    //
+                    for(i=m-1; i>=0; i--)
+                    {
+                        if( isunit )
+                        {
+                            vd = 1;
+                        }
+                        else
+                        {
+                            vd = 1/a[i1+i,j1+i];
+                        }
+                        for(i_=j2; i_<=j2+n-1;i_++)
+                        {
+                            x[i2+i,i_] = vd*x[i2+i,i_];
+                        }
+                        for(j=i-1; j>=0; j--)
+                        {
+                            vr = a[i1+i,j1+j];
+                            for(i_=j2; i_<=j2+n-1;i_++)
+                            {
+                                x[i2+j,i_] = x[i2+j,i_] - vr*x[i2+i,i_];
+                            }
+                        }
+                    }
+                    return;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Level 2 subroutine
+        *************************************************************************/
+        private static void cmatrixsyrk2(int n,
+            int k,
+            double alpha,
+            complex[,] a,
+            int ia,
+            int ja,
+            int optypea,
+            double beta,
+            ref complex[,] c,
+            int ic,
+            int jc,
+            bool isupper)
+        {
+            int i = 0;
+            int j = 0;
+            int j1 = 0;
+            int j2 = 0;
+            complex v = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            
+            //
+            // Fast exit (nothing to be done)
+            //
+            if( ((double)(alpha)==(double)(0) || k==0) && (double)(beta)==(double)(1) )
+            {
+                return;
+            }
+            
+            //
+            // Try to call fast SYRK
+            //
+            if( ablasf.cmatrixsyrkf(n, k, alpha, a, ia, ja, optypea, beta, ref c, ic, jc, isupper) )
+            {
+                return;
+            }
+            
+            //
+            // SYRK
+            //
+            if( optypea==0 )
+            {
+                
+                //
+                // C=alpha*A*A^H+beta*C
+                //
+                for(i=0; i<=n-1; i++)
+                {
+                    if( isupper )
+                    {
+                        j1 = i;
+                        j2 = n-1;
+                    }
+                    else
+                    {
+                        j1 = 0;
+                        j2 = i;
+                    }
+                    for(j=j1; j<=j2; j++)
+                    {
+                        if( (double)(alpha)!=(double)(0) && k>0 )
+                        {
+                            v = 0.0;
+                            for(i_=ja; i_<=ja+k-1;i_++)
+                            {
+                                v += a[ia+i,i_]*math.conj(a[ia+j,i_]);
+                            }
+                        }
+                        else
+                        {
+                            v = 0;
+                        }
+                        if( (double)(beta)==(double)(0) )
+                        {
+                            c[ic+i,jc+j] = alpha*v;
+                        }
+                        else
+                        {
+                            c[ic+i,jc+j] = beta*c[ic+i,jc+j]+alpha*v;
+                        }
+                    }
+                }
+                return;
+            }
+            else
+            {
+                
+                //
+                // C=alpha*A^H*A+beta*C
+                //
+                for(i=0; i<=n-1; i++)
+                {
+                    if( isupper )
+                    {
+                        j1 = i;
+                        j2 = n-1;
+                    }
+                    else
+                    {
+                        j1 = 0;
+                        j2 = i;
+                    }
+                    if( (double)(beta)==(double)(0) )
+                    {
+                        for(j=j1; j<=j2; j++)
+                        {
+                            c[ic+i,jc+j] = 0;
+                        }
+                    }
+                    else
+                    {
+                        for(i_=jc+j1; i_<=jc+j2;i_++)
+                        {
+                            c[ic+i,i_] = beta*c[ic+i,i_];
+                        }
+                    }
+                }
+                for(i=0; i<=k-1; i++)
+                {
+                    for(j=0; j<=n-1; j++)
+                    {
+                        if( isupper )
+                        {
+                            j1 = j;
+                            j2 = n-1;
+                        }
+                        else
+                        {
+                            j1 = 0;
+                            j2 = j;
+                        }
+                        v = alpha*math.conj(a[ia+i,ja+j]);
+                        i1_ = (ja+j1) - (jc+j1);
+                        for(i_=jc+j1; i_<=jc+j2;i_++)
+                        {
+                            c[ic+j,i_] = c[ic+j,i_] + v*a[ia+i,i_+i1_];
+                        }
+                    }
+                }
+                return;
+            }
+        }
+
+
+        /*************************************************************************
+        Level 2 subrotuine
+        *************************************************************************/
+        private static void rmatrixsyrk2(int n,
+            int k,
+            double alpha,
+            double[,] a,
+            int ia,
+            int ja,
+            int optypea,
+            double beta,
+            ref double[,] c,
+            int ic,
+            int jc,
+            bool isupper)
+        {
+            int i = 0;
+            int j = 0;
+            int j1 = 0;
+            int j2 = 0;
+            double v = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            
+            //
+            // Fast exit (nothing to be done)
+            //
+            if( ((double)(alpha)==(double)(0) || k==0) && (double)(beta)==(double)(1) )
+            {
+                return;
+            }
+            
+            //
+            // Try to call fast SYRK
+            //
+            if( ablasf.rmatrixsyrkf(n, k, alpha, a, ia, ja, optypea, beta, ref c, ic, jc, isupper) )
+            {
+                return;
+            }
+            
+            //
+            // SYRK
+            //
+            if( optypea==0 )
+            {
+                
+                //
+                // C=alpha*A*A^H+beta*C
+                //
+                for(i=0; i<=n-1; i++)
+                {
+                    if( isupper )
+                    {
+                        j1 = i;
+                        j2 = n-1;
+                    }
+                    else
+                    {
+                        j1 = 0;
+                        j2 = i;
+                    }
+                    for(j=j1; j<=j2; j++)
+                    {
+                        if( (double)(alpha)!=(double)(0) && k>0 )
+                        {
+                            v = 0.0;
+                            for(i_=ja; i_<=ja+k-1;i_++)
+                            {
+                                v += a[ia+i,i_]*a[ia+j,i_];
+                            }
+                        }
+                        else
+                        {
+                            v = 0;
+                        }
+                        if( (double)(beta)==(double)(0) )
+                        {
+                            c[ic+i,jc+j] = alpha*v;
+                        }
+                        else
+                        {
+                            c[ic+i,jc+j] = beta*c[ic+i,jc+j]+alpha*v;
+                        }
+                    }
+                }
+                return;
+            }
+            else
+            {
+                
+                //
+                // C=alpha*A^H*A+beta*C
+                //
+                for(i=0; i<=n-1; i++)
+                {
+                    if( isupper )
+                    {
+                        j1 = i;
+                        j2 = n-1;
+                    }
+                    else
+                    {
+                        j1 = 0;
+                        j2 = i;
+                    }
+                    if( (double)(beta)==(double)(0) )
+                    {
+                        for(j=j1; j<=j2; j++)
+                        {
+                            c[ic+i,jc+j] = 0;
+                        }
+                    }
+                    else
+                    {
+                        for(i_=jc+j1; i_<=jc+j2;i_++)
+                        {
+                            c[ic+i,i_] = beta*c[ic+i,i_];
+                        }
+                    }
+                }
+                for(i=0; i<=k-1; i++)
+                {
+                    for(j=0; j<=n-1; j++)
+                    {
+                        if( isupper )
+                        {
+                            j1 = j;
+                            j2 = n-1;
+                        }
+                        else
+                        {
+                            j1 = 0;
+                            j2 = j;
+                        }
+                        v = alpha*a[ia+i,ja+j];
+                        i1_ = (ja+j1) - (jc+j1);
+                        for(i_=jc+j1; i_<=jc+j2;i_++)
+                        {
+                            c[ic+j,i_] = c[ic+j,i_] + v*a[ia+i,i_+i1_];
+                        }
+                    }
+                }
+                return;
+            }
+        }
+
+
+        /*************************************************************************
+        GEMM kernel
+
+          -- ALGLIB routine --
+             16.12.2009
+             Bochkanov Sergey
+        *************************************************************************/
+        private static void cmatrixgemmk(int m,
+            int n,
+            int k,
+            complex alpha,
+            complex[,] a,
+            int ia,
+            int ja,
+            int optypea,
+            complex[,] b,
+            int ib,
+            int jb,
+            int optypeb,
+            complex beta,
+            ref complex[,] c,
+            int ic,
+            int jc)
+        {
+            int i = 0;
+            int j = 0;
+            complex v = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            
+            //
+            // Special case
+            //
+            if( m*n==0 )
+            {
+                return;
+            }
+            
+            //
+            // Try optimized code
+            //
+            if( ablasf.cmatrixgemmf(m, n, k, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, ref c, ic, jc) )
+            {
+                return;
+            }
+            
+            //
+            // Another special case
+            //
+            if( k==0 )
+            {
+                if( beta!=0 )
+                {
+                    for(i=0; i<=m-1; i++)
+                    {
+                        for(j=0; j<=n-1; j++)
+                        {
+                            c[ic+i,jc+j] = beta*c[ic+i,jc+j];
+                        }
+                    }
+                }
+                else
+                {
+                    for(i=0; i<=m-1; i++)
+                    {
+                        for(j=0; j<=n-1; j++)
+                        {
+                            c[ic+i,jc+j] = 0;
+                        }
+                    }
+                }
+                return;
+            }
+            
+            //
+            // General case
+            //
+            if( optypea==0 && optypeb!=0 )
+            {
+                
+                //
+                // A*B'
+                //
+                for(i=0; i<=m-1; i++)
+                {
+                    for(j=0; j<=n-1; j++)
+                    {
+                        if( k==0 || alpha==0 )
+                        {
+                            v = 0;
+                        }
+                        else
+                        {
+                            if( optypeb==1 )
+                            {
+                                i1_ = (jb)-(ja);
+                                v = 0.0;
+                                for(i_=ja; i_<=ja+k-1;i_++)
+                                {
+                                    v += a[ia+i,i_]*b[ib+j,i_+i1_];
+                                }
+                            }
+                            else
+                            {
+                                i1_ = (jb)-(ja);
+                                v = 0.0;
+                                for(i_=ja; i_<=ja+k-1;i_++)
+                                {
+                                    v += a[ia+i,i_]*math.conj(b[ib+j,i_+i1_]);
+                                }
+                            }
+                        }
+                        if( beta==0 )
+                        {
+                            c[ic+i,jc+j] = alpha*v;
+                        }
+                        else
+                        {
+                            c[ic+i,jc+j] = beta*c[ic+i,jc+j]+alpha*v;
+                        }
+                    }
+                }
+                return;
+            }
+            if( optypea==0 && optypeb==0 )
+            {
+                
+                //
+                // A*B
+                //
+                for(i=0; i<=m-1; i++)
+                {
+                    if( beta!=0 )
+                    {
+                        for(i_=jc; i_<=jc+n-1;i_++)
+                        {
+                            c[ic+i,i_] = beta*c[ic+i,i_];
+                        }
+                    }
+                    else
+                    {
+                        for(j=0; j<=n-1; j++)
+                        {
+                            c[ic+i,jc+j] = 0;
+                        }
+                    }
+                    if( alpha!=0 )
+                    {
+                        for(j=0; j<=k-1; j++)
+                        {
+                            v = alpha*a[ia+i,ja+j];
+                            i1_ = (jb) - (jc);
+                            for(i_=jc; i_<=jc+n-1;i_++)
+                            {
+                                c[ic+i,i_] = c[ic+i,i_] + v*b[ib+j,i_+i1_];
+                            }
+                        }
+                    }
+                }
+                return;
+            }
+            if( optypea!=0 && optypeb!=0 )
+            {
+                
+                //
+                // A'*B'
+                //
+                for(i=0; i<=m-1; i++)
+                {
+                    for(j=0; j<=n-1; j++)
+                    {
+                        if( alpha==0 )
+                        {
+                            v = 0;
+                        }
+                        else
+                        {
+                            if( optypea==1 )
+                            {
+                                if( optypeb==1 )
+                                {
+                                    i1_ = (jb)-(ia);
+                                    v = 0.0;
+                                    for(i_=ia; i_<=ia+k-1;i_++)
+                                    {
+                                        v += a[i_,ja+i]*b[ib+j,i_+i1_];
+                                    }
+                                }
+                                else
+                                {
+                                    i1_ = (jb)-(ia);
+                                    v = 0.0;
+                                    for(i_=ia; i_<=ia+k-1;i_++)
+                                    {
+                                        v += a[i_,ja+i]*math.conj(b[ib+j,i_+i1_]);
+                                    }
+                                }
+                            }
+                            else
+                            {
+                                if( optypeb==1 )
+                                {
+                                    i1_ = (jb)-(ia);
+                                    v = 0.0;
+                                    for(i_=ia; i_<=ia+k-1;i_++)
+                                    {
+                                        v += math.conj(a[i_,ja+i])*b[ib+j,i_+i1_];
+                                    }
+                                }
+                                else
+                                {
+                                    i1_ = (jb)-(ia);
+                                    v = 0.0;
+                                    for(i_=ia; i_<=ia+k-1;i_++)
+                                    {
+                                        v += math.conj(a[i_,ja+i])*math.conj(b[ib+j,i_+i1_]);
+                                    }
+                                }
+                            }
+                        }
+                        if( beta==0 )
+                        {
+                            c[ic+i,jc+j] = alpha*v;
+                        }
+                        else
+                        {
+                            c[ic+i,jc+j] = beta*c[ic+i,jc+j]+alpha*v;
+                        }
+                    }
+                }
+                return;
+            }
+            if( optypea!=0 && optypeb==0 )
+            {
+                
+                //
+                // A'*B
+                //
+                if( beta==0 )
+                {
+                    for(i=0; i<=m-1; i++)
+                    {
+                        for(j=0; j<=n-1; j++)
+                        {
+                            c[ic+i,jc+j] = 0;
+                        }
+                    }
+                }
+                else
+                {
+                    for(i=0; i<=m-1; i++)
+                    {
+                        for(i_=jc; i_<=jc+n-1;i_++)
+                        {
+                            c[ic+i,i_] = beta*c[ic+i,i_];
+                        }
+                    }
+                }
+                if( alpha!=0 )
+                {
+                    for(j=0; j<=k-1; j++)
+                    {
+                        for(i=0; i<=m-1; i++)
+                        {
+                            if( optypea==1 )
+                            {
+                                v = alpha*a[ia+j,ja+i];
+                            }
+                            else
+                            {
+                                v = alpha*math.conj(a[ia+j,ja+i]);
+                            }
+                            i1_ = (jb) - (jc);
+                            for(i_=jc; i_<=jc+n-1;i_++)
+                            {
+                                c[ic+i,i_] = c[ic+i,i_] + v*b[ib+j,i_+i1_];
+                            }
+                        }
+                    }
+                }
+                return;
+            }
+        }
+
+
+        /*************************************************************************
+        GEMM kernel
+
+          -- ALGLIB routine --
+             16.12.2009
+             Bochkanov Sergey
+        *************************************************************************/
+        private static void rmatrixgemmk(int m,
+            int n,
+            int k,
+            double alpha,
+            double[,] a,
+            int ia,
+            int ja,
+            int optypea,
+            double[,] b,
+            int ib,
+            int jb,
+            int optypeb,
+            double beta,
+            ref double[,] c,
+            int ic,
+            int jc)
+        {
+            int i = 0;
+            int j = 0;
+            double v = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            
+            //
+            // if matrix size is zero
+            //
+            if( m*n==0 )
+            {
+                return;
+            }
+            
+            //
+            // Try optimized code
+            //
+            if( ablasf.rmatrixgemmf(m, n, k, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, ref c, ic, jc) )
+            {
+                return;
+            }
+            
+            //
+            // if K=0, then C=Beta*C
+            //
+            if( k==0 )
+            {
+                if( (double)(beta)!=(double)(1) )
+                {
+                    if( (double)(beta)!=(double)(0) )
+                    {
+                        for(i=0; i<=m-1; i++)
+                        {
+                            for(j=0; j<=n-1; j++)
+                            {
+                                c[ic+i,jc+j] = beta*c[ic+i,jc+j];
+                            }
+                        }
+                    }
+                    else
+                    {
+                        for(i=0; i<=m-1; i++)
+                        {
+                            for(j=0; j<=n-1; j++)
+                            {
+                                c[ic+i,jc+j] = 0;
+                            }
+                        }
+                    }
+                }
+                return;
+            }
+            
+            //
+            // General case
+            //
+            if( optypea==0 && optypeb!=0 )
+            {
+                
+                //
+                // A*B'
+                //
+                for(i=0; i<=m-1; i++)
+                {
+                    for(j=0; j<=n-1; j++)
+                    {
+                        if( k==0 || (double)(alpha)==(double)(0) )
+                        {
+                            v = 0;
+                        }
+                        else
+                        {
+                            i1_ = (jb)-(ja);
+                            v = 0.0;
+                            for(i_=ja; i_<=ja+k-1;i_++)
+                            {
+                                v += a[ia+i,i_]*b[ib+j,i_+i1_];
+                            }
+                        }
+                        if( (double)(beta)==(double)(0) )
+                        {
+                            c[ic+i,jc+j] = alpha*v;
+                        }
+                        else
+                        {
+                            c[ic+i,jc+j] = beta*c[ic+i,jc+j]+alpha*v;
+                        }
+                    }
+                }
+                return;
+            }
+            if( optypea==0 && optypeb==0 )
+            {
+                
+                //
+                // A*B
+                //
+                for(i=0; i<=m-1; i++)
+                {
+                    if( (double)(beta)!=(double)(0) )
+                    {
+                        for(i_=jc; i_<=jc+n-1;i_++)
+                        {
+                            c[ic+i,i_] = beta*c[ic+i,i_];
+                        }
+                    }
+                    else
+                    {
+                        for(j=0; j<=n-1; j++)
+                        {
+                            c[ic+i,jc+j] = 0;
+                        }
+                    }
+                    if( (double)(alpha)!=(double)(0) )
+                    {
+                        for(j=0; j<=k-1; j++)
+                        {
+                            v = alpha*a[ia+i,ja+j];
+                            i1_ = (jb) - (jc);
+                            for(i_=jc; i_<=jc+n-1;i_++)
+                            {
+                                c[ic+i,i_] = c[ic+i,i_] + v*b[ib+j,i_+i1_];
+                            }
+                        }
+                    }
+                }
+                return;
+            }
+            if( optypea!=0 && optypeb!=0 )
+            {
+                
+                //
+                // A'*B'
+                //
+                for(i=0; i<=m-1; i++)
+                {
+                    for(j=0; j<=n-1; j++)
+                    {
+                        if( (double)(alpha)==(double)(0) )
+                        {
+                            v = 0;
+                        }
+                        else
+                        {
+                            i1_ = (jb)-(ia);
+                            v = 0.0;
+                            for(i_=ia; i_<=ia+k-1;i_++)
+                            {
+                                v += a[i_,ja+i]*b[ib+j,i_+i1_];
+                            }
+                        }
+                        if( (double)(beta)==(double)(0) )
+                        {
+                            c[ic+i,jc+j] = alpha*v;
+                        }
+                        else
+                        {
+                            c[ic+i,jc+j] = beta*c[ic+i,jc+j]+alpha*v;
+                        }
+                    }
+                }
+                return;
+            }
+            if( optypea!=0 && optypeb==0 )
+            {
+                
+                //
+                // A'*B
+                //
+                if( (double)(beta)==(double)(0) )
+                {
+                    for(i=0; i<=m-1; i++)
+                    {
+                        for(j=0; j<=n-1; j++)
+                        {
+                            c[ic+i,jc+j] = 0;
+                        }
+                    }
+                }
+                else
+                {
+                    for(i=0; i<=m-1; i++)
+                    {
+                        for(i_=jc; i_<=jc+n-1;i_++)
+                        {
+                            c[ic+i,i_] = beta*c[ic+i,i_];
+                        }
+                    }
+                }
+                if( (double)(alpha)!=(double)(0) )
+                {
+                    for(j=0; j<=k-1; j++)
+                    {
+                        for(i=0; i<=m-1; i++)
+                        {
+                            v = alpha*a[ia+j,ja+i];
+                            i1_ = (jb) - (jc);
+                            for(i_=jc; i_<=jc+n-1;i_++)
+                            {
+                                c[ic+i,i_] = c[ic+i,i_] + v*b[ib+j,i_+i1_];
+                            }
+                        }
+                    }
+                }
+                return;
+            }
+        }
+
+
+    }
+    public class ortfac
+    {
+        /*************************************************************************
+        QR decomposition of a rectangular matrix of size MxN
+
+        Input parameters:
+            A   -   matrix A whose indexes range within [0..M-1, 0..N-1].
+            M   -   number of rows in matrix A.
+            N   -   number of columns in matrix A.
+
+        Output parameters:
+            A   -   matrices Q and R in compact form (see below).
+            Tau -   array of scalar factors which are used to form
+                    matrix Q. Array whose index ranges within [0.. Min(M-1,N-1)].
+
+        Matrix A is represented as A = QR, where Q is an orthogonal matrix of size
+        MxM, R - upper triangular (or upper trapezoid) matrix of size M x N.
+
+        The elements of matrix R are located on and above the main diagonal of
+        matrix A. The elements which are located in Tau array and below the main
+        diagonal of matrix A are used to form matrix Q as follows:
+
+        Matrix Q is represented as a product of elementary reflections
+
+        Q = H(0)*H(2)*...*H(k-1),
+
+        where k = min(m,n), and each H(i) is in the form
+
+        H(i) = 1 - tau * v * (v^T)
+
+        where tau is a scalar stored in Tau[I]; v - real vector,
+        so that v(0:i-1) = 0, v(i) = 1, v(i+1:m-1) stored in A(i+1:m-1,i).
+
+          -- ALGLIB routine --
+             17.02.2010
+             Bochkanov Sergey
+        *************************************************************************/
+        public static void rmatrixqr(ref double[,] a,
+            int m,
+            int n,
+            ref double[] tau)
+        {
+            double[] work = new double[0];
+            double[] t = new double[0];
+            double[] taubuf = new double[0];
+            int minmn = 0;
+            double[,] tmpa = new double[0,0];
+            double[,] tmpt = new double[0,0];
+            double[,] tmpr = new double[0,0];
+            int blockstart = 0;
+            int blocksize = 0;
+            int rowscount = 0;
+            int i = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            tau = new double[0];
+
+            if( m<=0 || n<=0 )
+            {
+                return;
+            }
+            minmn = Math.Min(m, n);
+            work = new double[Math.Max(m, n)+1];
+            t = new double[Math.Max(m, n)+1];
+            tau = new double[minmn];
+            taubuf = new double[minmn];
+            tmpa = new double[m, ablas.ablasblocksize(a)];
+            tmpt = new double[ablas.ablasblocksize(a), 2*ablas.ablasblocksize(a)];
+            tmpr = new double[2*ablas.ablasblocksize(a), n];
+            
+            //
+            // Blocked code
+            //
+            blockstart = 0;
+            while( blockstart!=minmn )
+            {
+                
+                //
+                // Determine block size
+                //
+                blocksize = minmn-blockstart;
+                if( blocksize>ablas.ablasblocksize(a) )
+                {
+                    blocksize = ablas.ablasblocksize(a);
+                }
+                rowscount = m-blockstart;
+                
+                //
+                // QR decomposition of submatrix.
+                // Matrix is copied to temporary storage to solve
+                // some TLB issues arising from non-contiguous memory
+                // access pattern.
+                //
+                ablas.rmatrixcopy(rowscount, blocksize, a, blockstart, blockstart, ref tmpa, 0, 0);
+                rmatrixqrbasecase(ref tmpa, rowscount, blocksize, ref work, ref t, ref taubuf);
+                ablas.rmatrixcopy(rowscount, blocksize, tmpa, 0, 0, ref a, blockstart, blockstart);
+                i1_ = (0) - (blockstart);
+                for(i_=blockstart; i_<=blockstart+blocksize-1;i_++)
+                {
+                    tau[i_] = taubuf[i_+i1_];
+                }
+                
+                //
+                // Update the rest, choose between:
+                // a) Level 2 algorithm (when the rest of the matrix is small enough)
+                // b) blocked algorithm, see algorithm 5 from  'A storage efficient WY
+                //    representation for products of Householder transformations',
+                //    by R. Schreiber and C. Van Loan.
+                //
+                if( blockstart+blocksize<=n-1 )
+                {
+                    if( n-blockstart-blocksize>=2*ablas.ablasblocksize(a) || rowscount>=4*ablas.ablasblocksize(a) )
+                    {
+                        
+                        //
+                        // Prepare block reflector
+                        //
+                        rmatrixblockreflector(ref tmpa, ref taubuf, true, rowscount, blocksize, ref tmpt, ref work);
+                        
+                        //
+                        // Multiply the rest of A by Q'.
+                        //
+                        // Q  = E + Y*T*Y'  = E + TmpA*TmpT*TmpA'
+                        // Q' = E + Y*T'*Y' = E + TmpA*TmpT'*TmpA'
+                        //
+                        ablas.rmatrixgemm(blocksize, n-blockstart-blocksize, rowscount, 1.0, tmpa, 0, 0, 1, a, blockstart, blockstart+blocksize, 0, 0.0, ref tmpr, 0, 0);
+                        ablas.rmatrixgemm(blocksize, n-blockstart-blocksize, blocksize, 1.0, tmpt, 0, 0, 1, tmpr, 0, 0, 0, 0.0, ref tmpr, blocksize, 0);
+                        ablas.rmatrixgemm(rowscount, n-blockstart-blocksize, blocksize, 1.0, tmpa, 0, 0, 0, tmpr, blocksize, 0, 0, 1.0, ref a, blockstart, blockstart+blocksize);
+                    }
+                    else
+                    {
+                        
+                        //
+                        // Level 2 algorithm
+                        //
+                        for(i=0; i<=blocksize-1; i++)
+                        {
+                            i1_ = (i) - (1);
+                            for(i_=1; i_<=rowscount-i;i_++)
+                            {
+                                t[i_] = tmpa[i_+i1_,i];
+                            }
+                            t[1] = 1;
+                            reflections.applyreflectionfromtheleft(ref a, taubuf[i], t, blockstart+i, m-1, blockstart+blocksize, n-1, ref work);
+                        }
+                    }
+                }
+                
+                //
+                // Advance
+                //
+                blockstart = blockstart+blocksize;
+            }
+        }
+
+
+        /*************************************************************************
+        LQ decomposition of a rectangular matrix of size MxN
+
+        Input parameters:
+            A   -   matrix A whose indexes range within [0..M-1, 0..N-1].
+            M   -   number of rows in matrix A.
+            N   -   number of columns in matrix A.
+
+        Output parameters:
+            A   -   matrices L and Q in compact form (see below)
+            Tau -   array of scalar factors which are used to form
+                    matrix Q. Array whose index ranges within [0..Min(M,N)-1].
+
+        Matrix A is represented as A = LQ, where Q is an orthogonal matrix of size
+        MxM, L - lower triangular (or lower trapezoid) matrix of size M x N.
+
+        The elements of matrix L are located on and below  the  main  diagonal  of
+        matrix A. The elements which are located in Tau array and above  the  main
+        diagonal of matrix A are used to form matrix Q as follows:
+
+        Matrix Q is represented as a product of elementary reflections
+
+        Q = H(k-1)*H(k-2)*...*H(1)*H(0),
+
+        where k = min(m,n), and each H(i) is of the form
+
+        H(i) = 1 - tau * v * (v^T)
+
+        where tau is a scalar stored in Tau[I]; v - real vector, so that v(0:i-1)=0,
+        v(i) = 1, v(i+1:n-1) stored in A(i,i+1:n-1).
+
+          -- ALGLIB routine --
+             17.02.2010
+             Bochkanov Sergey
+        *************************************************************************/
+        public static void rmatrixlq(ref double[,] a,
+            int m,
+            int n,
+            ref double[] tau)
+        {
+            double[] work = new double[0];
+            double[] t = new double[0];
+            double[] taubuf = new double[0];
+            int minmn = 0;
+            double[,] tmpa = new double[0,0];
+            double[,] tmpt = new double[0,0];
+            double[,] tmpr = new double[0,0];
+            int blockstart = 0;
+            int blocksize = 0;
+            int columnscount = 0;
+            int i = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            tau = new double[0];
+
+            if( m<=0 || n<=0 )
+            {
+                return;
+            }
+            minmn = Math.Min(m, n);
+            work = new double[Math.Max(m, n)+1];
+            t = new double[Math.Max(m, n)+1];
+            tau = new double[minmn];
+            taubuf = new double[minmn];
+            tmpa = new double[ablas.ablasblocksize(a), n];
+            tmpt = new double[ablas.ablasblocksize(a), 2*ablas.ablasblocksize(a)];
+            tmpr = new double[m, 2*ablas.ablasblocksize(a)];
+            
+            //
+            // Blocked code
+            //
+            blockstart = 0;
+            while( blockstart!=minmn )
+            {
+                
+                //
+                // Determine block size
+                //
+                blocksize = minmn-blockstart;
+                if( blocksize>ablas.ablasblocksize(a) )
+                {
+                    blocksize = ablas.ablasblocksize(a);
+                }
+                columnscount = n-blockstart;
+                
+                //
+                // LQ decomposition of submatrix.
+                // Matrix is copied to temporary storage to solve
+                // some TLB issues arising from non-contiguous memory
+                // access pattern.
+                //
+                ablas.rmatrixcopy(blocksize, columnscount, a, blockstart, blockstart, ref tmpa, 0, 0);
+                rmatrixlqbasecase(ref tmpa, blocksize, columnscount, ref work, ref t, ref taubuf);
+                ablas.rmatrixcopy(blocksize, columnscount, tmpa, 0, 0, ref a, blockstart, blockstart);
+                i1_ = (0) - (blockstart);
+                for(i_=blockstart; i_<=blockstart+blocksize-1;i_++)
+                {
+                    tau[i_] = taubuf[i_+i1_];
+                }
+                
+                //
+                // Update the rest, choose between:
+                // a) Level 2 algorithm (when the rest of the matrix is small enough)
+                // b) blocked algorithm, see algorithm 5 from  'A storage efficient WY
+                //    representation for products of Householder transformations',
+                //    by R. Schreiber and C. Van Loan.
+                //
+                if( blockstart+blocksize<=m-1 )
+                {
+                    if( m-blockstart-blocksize>=2*ablas.ablasblocksize(a) )
+                    {
+                        
+                        //
+                        // Prepare block reflector
+                        //
+                        rmatrixblockreflector(ref tmpa, ref taubuf, false, columnscount, blocksize, ref tmpt, ref work);
+                        
+                        //
+                        // Multiply the rest of A by Q.
+                        //
+                        // Q  = E + Y*T*Y'  = E + TmpA'*TmpT*TmpA
+                        //
+                        ablas.rmatrixgemm(m-blockstart-blocksize, blocksize, columnscount, 1.0, a, blockstart+blocksize, blockstart, 0, tmpa, 0, 0, 1, 0.0, ref tmpr, 0, 0);
+                        ablas.rmatrixgemm(m-blockstart-blocksize, blocksize, blocksize, 1.0, tmpr, 0, 0, 0, tmpt, 0, 0, 0, 0.0, ref tmpr, 0, blocksize);
+                        ablas.rmatrixgemm(m-blockstart-blocksize, columnscount, blocksize, 1.0, tmpr, 0, blocksize, 0, tmpa, 0, 0, 0, 1.0, ref a, blockstart+blocksize, blockstart);
+                    }
+                    else
+                    {
+                        
+                        //
+                        // Level 2 algorithm
+                        //
+                        for(i=0; i<=blocksize-1; i++)
+                        {
+                            i1_ = (i) - (1);
+                            for(i_=1; i_<=columnscount-i;i_++)
+                            {
+                                t[i_] = tmpa[i,i_+i1_];
+                            }
+                            t[1] = 1;
+                            reflections.applyreflectionfromtheright(ref a, taubuf[i], t, blockstart+blocksize, m-1, blockstart+i, n-1, ref work);
+                        }
+                    }
+                }
+                
+                //
+                // Advance
+                //
+                blockstart = blockstart+blocksize;
+            }
+        }
+
+
+        /*************************************************************************
+        QR decomposition of a rectangular complex matrix of size MxN
+
+        Input parameters:
+            A   -   matrix A whose indexes range within [0..M-1, 0..N-1]
+            M   -   number of rows in matrix A.
+            N   -   number of columns in matrix A.
+
+        Output parameters:
+            A   -   matrices Q and R in compact form
+            Tau -   array of scalar factors which are used to form matrix Q. Array
+                    whose indexes range within [0.. Min(M,N)-1]
+
+        Matrix A is represented as A = QR, where Q is an orthogonal matrix of size
+        MxM, R - upper triangular (or upper trapezoid) matrix of size MxN.
+
+          -- LAPACK routine (version 3.0) --
+             Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd.,
+             Courant Institute, Argonne National Lab, and Rice University
+             September 30, 1994
+        *************************************************************************/
+        public static void cmatrixqr(ref complex[,] a,
+            int m,
+            int n,
+            ref complex[] tau)
+        {
+            complex[] work = new complex[0];
+            complex[] t = new complex[0];
+            complex[] taubuf = new complex[0];
+            int minmn = 0;
+            complex[,] tmpa = new complex[0,0];
+            complex[,] tmpt = new complex[0,0];
+            complex[,] tmpr = new complex[0,0];
+            int blockstart = 0;
+            int blocksize = 0;
+            int rowscount = 0;
+            int i = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            tau = new complex[0];
+
+            if( m<=0 || n<=0 )
+            {
+                return;
+            }
+            minmn = Math.Min(m, n);
+            work = new complex[Math.Max(m, n)+1];
+            t = new complex[Math.Max(m, n)+1];
+            tau = new complex[minmn];
+            taubuf = new complex[minmn];
+            tmpa = new complex[m, ablas.ablascomplexblocksize(a)];
+            tmpt = new complex[ablas.ablascomplexblocksize(a), ablas.ablascomplexblocksize(a)];
+            tmpr = new complex[2*ablas.ablascomplexblocksize(a), n];
+            
+            //
+            // Blocked code
+            //
+            blockstart = 0;
+            while( blockstart!=minmn )
+            {
+                
+                //
+                // Determine block size
+                //
+                blocksize = minmn-blockstart;
+                if( blocksize>ablas.ablascomplexblocksize(a) )
+                {
+                    blocksize = ablas.ablascomplexblocksize(a);
+                }
+                rowscount = m-blockstart;
+                
+                //
+                // QR decomposition of submatrix.
+                // Matrix is copied to temporary storage to solve
+                // some TLB issues arising from non-contiguous memory
+                // access pattern.
+                //
+                ablas.cmatrixcopy(rowscount, blocksize, a, blockstart, blockstart, ref tmpa, 0, 0);
+                cmatrixqrbasecase(ref tmpa, rowscount, blocksize, ref work, ref t, ref taubuf);
+                ablas.cmatrixcopy(rowscount, blocksize, tmpa, 0, 0, ref a, blockstart, blockstart);
+                i1_ = (0) - (blockstart);
+                for(i_=blockstart; i_<=blockstart+blocksize-1;i_++)
+                {
+                    tau[i_] = taubuf[i_+i1_];
+                }
+                
+                //
+                // Update the rest, choose between:
+                // a) Level 2 algorithm (when the rest of the matrix is small enough)
+                // b) blocked algorithm, see algorithm 5 from  'A storage efficient WY
+                //    representation for products of Householder transformations',
+                //    by R. Schreiber and C. Van Loan.
+                //
+                if( blockstart+blocksize<=n-1 )
+                {
+                    if( n-blockstart-blocksize>=2*ablas.ablascomplexblocksize(a) )
+                    {
+                        
+                        //
+                        // Prepare block reflector
+                        //
+                        cmatrixblockreflector(ref tmpa, ref taubuf, true, rowscount, blocksize, ref tmpt, ref work);
+                        
+                        //
+                        // Multiply the rest of A by Q'.
+                        //
+                        // Q  = E + Y*T*Y'  = E + TmpA*TmpT*TmpA'
+                        // Q' = E + Y*T'*Y' = E + TmpA*TmpT'*TmpA'
+                        //
+                        ablas.cmatrixgemm(blocksize, n-blockstart-blocksize, rowscount, 1.0, tmpa, 0, 0, 2, a, blockstart, blockstart+blocksize, 0, 0.0, ref tmpr, 0, 0);
+                        ablas.cmatrixgemm(blocksize, n-blockstart-blocksize, blocksize, 1.0, tmpt, 0, 0, 2, tmpr, 0, 0, 0, 0.0, ref tmpr, blocksize, 0);
+                        ablas.cmatrixgemm(rowscount, n-blockstart-blocksize, blocksize, 1.0, tmpa, 0, 0, 0, tmpr, blocksize, 0, 0, 1.0, ref a, blockstart, blockstart+blocksize);
+                    }
+                    else
+                    {
+                        
+                        //
+                        // Level 2 algorithm
+                        //
+                        for(i=0; i<=blocksize-1; i++)
+                        {
+                            i1_ = (i) - (1);
+                            for(i_=1; i_<=rowscount-i;i_++)
+                            {
+                                t[i_] = tmpa[i_+i1_,i];
+                            }
+                            t[1] = 1;
+                            creflections.complexapplyreflectionfromtheleft(ref a, math.conj(taubuf[i]), t, blockstart+i, m-1, blockstart+blocksize, n-1, ref work);
+                        }
+                    }
+                }
+                
+                //
+                // Advance
+                //
+                blockstart = blockstart+blocksize;
+            }
+        }
+
+
+        /*************************************************************************
+        LQ decomposition of a rectangular complex matrix of size MxN
+
+        Input parameters:
+            A   -   matrix A whose indexes range within [0..M-1, 0..N-1]
+            M   -   number of rows in matrix A.
+            N   -   number of columns in matrix A.
+
+        Output parameters:
+            A   -   matrices Q and L in compact form
+            Tau -   array of scalar factors which are used to form matrix Q. Array
+                    whose indexes range within [0.. Min(M,N)-1]
+
+        Matrix A is represented as A = LQ, where Q is an orthogonal matrix of size
+        MxM, L - lower triangular (or lower trapezoid) matrix of size MxN.
+
+          -- LAPACK routine (version 3.0) --
+             Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd.,
+             Courant Institute, Argonne National Lab, and Rice University
+             September 30, 1994
+        *************************************************************************/
+        public static void cmatrixlq(ref complex[,] a,
+            int m,
+            int n,
+            ref complex[] tau)
+        {
+            complex[] work = new complex[0];
+            complex[] t = new complex[0];
+            complex[] taubuf = new complex[0];
+            int minmn = 0;
+            complex[,] tmpa = new complex[0,0];
+            complex[,] tmpt = new complex[0,0];
+            complex[,] tmpr = new complex[0,0];
+            int blockstart = 0;
+            int blocksize = 0;
+            int columnscount = 0;
+            int i = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            tau = new complex[0];
+
+            if( m<=0 || n<=0 )
+            {
+                return;
+            }
+            minmn = Math.Min(m, n);
+            work = new complex[Math.Max(m, n)+1];
+            t = new complex[Math.Max(m, n)+1];
+            tau = new complex[minmn];
+            taubuf = new complex[minmn];
+            tmpa = new complex[ablas.ablascomplexblocksize(a), n];
+            tmpt = new complex[ablas.ablascomplexblocksize(a), ablas.ablascomplexblocksize(a)];
+            tmpr = new complex[m, 2*ablas.ablascomplexblocksize(a)];
+            
+            //
+            // Blocked code
+            //
+            blockstart = 0;
+            while( blockstart!=minmn )
+            {
+                
+                //
+                // Determine block size
+                //
+                blocksize = minmn-blockstart;
+                if( blocksize>ablas.ablascomplexblocksize(a) )
+                {
+                    blocksize = ablas.ablascomplexblocksize(a);
+                }
+                columnscount = n-blockstart;
+                
+                //
+                // LQ decomposition of submatrix.
+                // Matrix is copied to temporary storage to solve
+                // some TLB issues arising from non-contiguous memory
+                // access pattern.
+                //
+                ablas.cmatrixcopy(blocksize, columnscount, a, blockstart, blockstart, ref tmpa, 0, 0);
+                cmatrixlqbasecase(ref tmpa, blocksize, columnscount, ref work, ref t, ref taubuf);
+                ablas.cmatrixcopy(blocksize, columnscount, tmpa, 0, 0, ref a, blockstart, blockstart);
+                i1_ = (0) - (blockstart);
+                for(i_=blockstart; i_<=blockstart+blocksize-1;i_++)
+                {
+                    tau[i_] = taubuf[i_+i1_];
+                }
+                
+                //
+                // Update the rest, choose between:
+                // a) Level 2 algorithm (when the rest of the matrix is small enough)
+                // b) blocked algorithm, see algorithm 5 from  'A storage efficient WY
+                //    representation for products of Householder transformations',
+                //    by R. Schreiber and C. Van Loan.
+                //
+                if( blockstart+blocksize<=m-1 )
+                {
+                    if( m-blockstart-blocksize>=2*ablas.ablascomplexblocksize(a) )
+                    {
+                        
+                        //
+                        // Prepare block reflector
+                        //
+                        cmatrixblockreflector(ref tmpa, ref taubuf, false, columnscount, blocksize, ref tmpt, ref work);
+                        
+                        //
+                        // Multiply the rest of A by Q.
+                        //
+                        // Q  = E + Y*T*Y'  = E + TmpA'*TmpT*TmpA
+                        //
+                        ablas.cmatrixgemm(m-blockstart-blocksize, blocksize, columnscount, 1.0, a, blockstart+blocksize, blockstart, 0, tmpa, 0, 0, 2, 0.0, ref tmpr, 0, 0);
+                        ablas.cmatrixgemm(m-blockstart-blocksize, blocksize, blocksize, 1.0, tmpr, 0, 0, 0, tmpt, 0, 0, 0, 0.0, ref tmpr, 0, blocksize);
+                        ablas.cmatrixgemm(m-blockstart-blocksize, columnscount, blocksize, 1.0, tmpr, 0, blocksize, 0, tmpa, 0, 0, 0, 1.0, ref a, blockstart+blocksize, blockstart);
+                    }
+                    else
+                    {
+                        
+                        //
+                        // Level 2 algorithm
+                        //
+                        for(i=0; i<=blocksize-1; i++)
+                        {
+                            i1_ = (i) - (1);
+                            for(i_=1; i_<=columnscount-i;i_++)
+                            {
+                                t[i_] = math.conj(tmpa[i,i_+i1_]);
+                            }
+                            t[1] = 1;
+                            creflections.complexapplyreflectionfromtheright(ref a, taubuf[i], ref t, blockstart+blocksize, m-1, blockstart+i, n-1, ref work);
+                        }
+                    }
+                }
+                
+                //
+                // Advance
+                //
+                blockstart = blockstart+blocksize;
+            }
+        }
+
+
+        /*************************************************************************
+        Partial unpacking of matrix Q from the QR decomposition of a matrix A
+
+        Input parameters:
+            A       -   matrices Q and R in compact form.
+                        Output of RMatrixQR subroutine.
+            M       -   number of rows in given matrix A. M>=0.
+            N       -   number of columns in given matrix A. N>=0.
+            Tau     -   scalar factors which are used to form Q.
+                        Output of the RMatrixQR subroutine.
+            QColumns -  required number of columns of matrix Q. M>=QColumns>=0.
+
+        Output parameters:
+            Q       -   first QColumns columns of matrix Q.
+                        Array whose indexes range within [0..M-1, 0..QColumns-1].
+                        If QColumns=0, the array remains unchanged.
+
+          -- ALGLIB routine --
+             17.02.2010
+             Bochkanov Sergey
+        *************************************************************************/
+        public static void rmatrixqrunpackq(double[,] a,
+            int m,
+            int n,
+            double[] tau,
+            int qcolumns,
+            ref double[,] q)
+        {
+            double[] work = new double[0];
+            double[] t = new double[0];
+            double[] taubuf = new double[0];
+            int minmn = 0;
+            int refcnt = 0;
+            double[,] tmpa = new double[0,0];
+            double[,] tmpt = new double[0,0];
+            double[,] tmpr = new double[0,0];
+            int blockstart = 0;
+            int blocksize = 0;
+            int rowscount = 0;
+            int i = 0;
+            int j = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            q = new double[0,0];
+
+            alglib.ap.assert(qcolumns<=m, "UnpackQFromQR: QColumns>M!");
+            if( (m<=0 || n<=0) || qcolumns<=0 )
+            {
+                return;
+            }
+            
+            //
+            // init
+            //
+            minmn = Math.Min(m, n);
+            refcnt = Math.Min(minmn, qcolumns);
+            q = new double[m, qcolumns];
+            for(i=0; i<=m-1; i++)
+            {
+                for(j=0; j<=qcolumns-1; j++)
+                {
+                    if( i==j )
+                    {
+                        q[i,j] = 1;
+                    }
+                    else
+                    {
+                        q[i,j] = 0;
+                    }
+                }
+            }
+            work = new double[Math.Max(m, qcolumns)+1];
+            t = new double[Math.Max(m, qcolumns)+1];
+            taubuf = new double[minmn];
+            tmpa = new double[m, ablas.ablasblocksize(a)];
+            tmpt = new double[ablas.ablasblocksize(a), 2*ablas.ablasblocksize(a)];
+            tmpr = new double[2*ablas.ablasblocksize(a), qcolumns];
+            
+            //
+            // Blocked code
+            //
+            blockstart = ablas.ablasblocksize(a)*(refcnt/ablas.ablasblocksize(a));
+            blocksize = refcnt-blockstart;
+            while( blockstart>=0 )
+            {
+                rowscount = m-blockstart;
+                if( blocksize>0 )
+                {
+                    
+                    //
+                    // Copy current block
+                    //
+                    ablas.rmatrixcopy(rowscount, blocksize, a, blockstart, blockstart, ref tmpa, 0, 0);
+                    i1_ = (blockstart) - (0);
+                    for(i_=0; i_<=blocksize-1;i_++)
+                    {
+                        taubuf[i_] = tau[i_+i1_];
+                    }
+                    
+                    //
+                    // Update, choose between:
+                    // a) Level 2 algorithm (when the rest of the matrix is small enough)
+                    // b) blocked algorithm, see algorithm 5 from  'A storage efficient WY
+                    //    representation for products of Householder transformations',
+                    //    by R. Schreiber and C. Van Loan.
+                    //
+                    if( qcolumns>=2*ablas.ablasblocksize(a) )
+                    {
+                        
+                        //
+                        // Prepare block reflector
+                        //
+                        rmatrixblockreflector(ref tmpa, ref taubuf, true, rowscount, blocksize, ref tmpt, ref work);
+                        
+                        //
+                        // Multiply matrix by Q.
+                        //
+                        // Q  = E + Y*T*Y'  = E + TmpA*TmpT*TmpA'
+                        //
+                        ablas.rmatrixgemm(blocksize, qcolumns, rowscount, 1.0, tmpa, 0, 0, 1, q, blockstart, 0, 0, 0.0, ref tmpr, 0, 0);
+                        ablas.rmatrixgemm(blocksize, qcolumns, blocksize, 1.0, tmpt, 0, 0, 0, tmpr, 0, 0, 0, 0.0, ref tmpr, blocksize, 0);
+                        ablas.rmatrixgemm(rowscount, qcolumns, blocksize, 1.0, tmpa, 0, 0, 0, tmpr, blocksize, 0, 0, 1.0, ref q, blockstart, 0);
+                    }
+                    else
+                    {
+                        
+                        //
+                        // Level 2 algorithm
+                        //
+                        for(i=blocksize-1; i>=0; i--)
+                        {
+                            i1_ = (i) - (1);
+                            for(i_=1; i_<=rowscount-i;i_++)
+                            {
+                                t[i_] = tmpa[i_+i1_,i];
+                            }
+                            t[1] = 1;
+                            reflections.applyreflectionfromtheleft(ref q, taubuf[i], t, blockstart+i, m-1, 0, qcolumns-1, ref work);
+                        }
+                    }
+                }
+                
+                //
+                // Advance
+                //
+                blockstart = blockstart-ablas.ablasblocksize(a);
+                blocksize = ablas.ablasblocksize(a);
+            }
+        }
+
+
+        /*************************************************************************
+        Unpacking of matrix R from the QR decomposition of a matrix A
+
+        Input parameters:
+            A       -   matrices Q and R in compact form.
+                        Output of RMatrixQR subroutine.
+            M       -   number of rows in given matrix A. M>=0.
+            N       -   number of columns in given matrix A. N>=0.
+
+        Output parameters:
+            R       -   matrix R, array[0..M-1, 0..N-1].
+
+          -- ALGLIB routine --
+             17.02.2010
+             Bochkanov Sergey
+        *************************************************************************/
+        public static void rmatrixqrunpackr(double[,] a,
+            int m,
+            int n,
+            ref double[,] r)
+        {
+            int i = 0;
+            int k = 0;
+            int i_ = 0;
+
+            r = new double[0,0];
+
+            if( m<=0 || n<=0 )
+            {
+                return;
+            }
+            k = Math.Min(m, n);
+            r = new double[m, n];
+            for(i=0; i<=n-1; i++)
+            {
+                r[0,i] = 0;
+            }
+            for(i=1; i<=m-1; i++)
+            {
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    r[i,i_] = r[0,i_];
+                }
+            }
+            for(i=0; i<=k-1; i++)
+            {
+                for(i_=i; i_<=n-1;i_++)
+                {
+                    r[i,i_] = a[i,i_];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Partial unpacking of matrix Q from the LQ decomposition of a matrix A
+
+        Input parameters:
+            A       -   matrices L and Q in compact form.
+                        Output of RMatrixLQ subroutine.
+            M       -   number of rows in given matrix A. M>=0.
+            N       -   number of columns in given matrix A. N>=0.
+            Tau     -   scalar factors which are used to form Q.
+                        Output of the RMatrixLQ subroutine.
+            QRows   -   required number of rows in matrix Q. N>=QRows>=0.
+
+        Output parameters:
+            Q       -   first QRows rows of matrix Q. Array whose indexes range
+                        within [0..QRows-1, 0..N-1]. If QRows=0, the array remains
+                        unchanged.
+
+          -- ALGLIB routine --
+             17.02.2010
+             Bochkanov Sergey
+        *************************************************************************/
+        public static void rmatrixlqunpackq(double[,] a,
+            int m,
+            int n,
+            double[] tau,
+            int qrows,
+            ref double[,] q)
+        {
+            double[] work = new double[0];
+            double[] t = new double[0];
+            double[] taubuf = new double[0];
+            int minmn = 0;
+            int refcnt = 0;
+            double[,] tmpa = new double[0,0];
+            double[,] tmpt = new double[0,0];
+            double[,] tmpr = new double[0,0];
+            int blockstart = 0;
+            int blocksize = 0;
+            int columnscount = 0;
+            int i = 0;
+            int j = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            q = new double[0,0];
+
+            alglib.ap.assert(qrows<=n, "RMatrixLQUnpackQ: QRows>N!");
+            if( (m<=0 || n<=0) || qrows<=0 )
+            {
+                return;
+            }
+            
+            //
+            // init
+            //
+            minmn = Math.Min(m, n);
+            refcnt = Math.Min(minmn, qrows);
+            work = new double[Math.Max(m, n)+1];
+            t = new double[Math.Max(m, n)+1];
+            taubuf = new double[minmn];
+            tmpa = new double[ablas.ablasblocksize(a), n];
+            tmpt = new double[ablas.ablasblocksize(a), 2*ablas.ablasblocksize(a)];
+            tmpr = new double[qrows, 2*ablas.ablasblocksize(a)];
+            q = new double[qrows, n];
+            for(i=0; i<=qrows-1; i++)
+            {
+                for(j=0; j<=n-1; j++)
+                {
+                    if( i==j )
+                    {
+                        q[i,j] = 1;
+                    }
+                    else
+                    {
+                        q[i,j] = 0;
+                    }
+                }
+            }
+            
+            //
+            // Blocked code
+            //
+            blockstart = ablas.ablasblocksize(a)*(refcnt/ablas.ablasblocksize(a));
+            blocksize = refcnt-blockstart;
+            while( blockstart>=0 )
+            {
+                columnscount = n-blockstart;
+                if( blocksize>0 )
+                {
+                    
+                    //
+                    // Copy submatrix
+                    //
+                    ablas.rmatrixcopy(blocksize, columnscount, a, blockstart, blockstart, ref tmpa, 0, 0);
+                    i1_ = (blockstart) - (0);
+                    for(i_=0; i_<=blocksize-1;i_++)
+                    {
+                        taubuf[i_] = tau[i_+i1_];
+                    }
+                    
+                    //
+                    // Update matrix, choose between:
+                    // a) Level 2 algorithm (when the rest of the matrix is small enough)
+                    // b) blocked algorithm, see algorithm 5 from  'A storage efficient WY
+                    //    representation for products of Householder transformations',
+                    //    by R. Schreiber and C. Van Loan.
+                    //
+                    if( qrows>=2*ablas.ablasblocksize(a) )
+                    {
+                        
+                        //
+                        // Prepare block reflector
+                        //
+                        rmatrixblockreflector(ref tmpa, ref taubuf, false, columnscount, blocksize, ref tmpt, ref work);
+                        
+                        //
+                        // Multiply the rest of A by Q'.
+                        //
+                        // Q'  = E + Y*T'*Y'  = E + TmpA'*TmpT'*TmpA
+                        //
+                        ablas.rmatrixgemm(qrows, blocksize, columnscount, 1.0, q, 0, blockstart, 0, tmpa, 0, 0, 1, 0.0, ref tmpr, 0, 0);
+                        ablas.rmatrixgemm(qrows, blocksize, blocksize, 1.0, tmpr, 0, 0, 0, tmpt, 0, 0, 1, 0.0, ref tmpr, 0, blocksize);
+                        ablas.rmatrixgemm(qrows, columnscount, blocksize, 1.0, tmpr, 0, blocksize, 0, tmpa, 0, 0, 0, 1.0, ref q, 0, blockstart);
+                    }
+                    else
+                    {
+                        
+                        //
+                        // Level 2 algorithm
+                        //
+                        for(i=blocksize-1; i>=0; i--)
+                        {
+                            i1_ = (i) - (1);
+                            for(i_=1; i_<=columnscount-i;i_++)
+                            {
+                                t[i_] = tmpa[i,i_+i1_];
+                            }
+                            t[1] = 1;
+                            reflections.applyreflectionfromtheright(ref q, taubuf[i], t, 0, qrows-1, blockstart+i, n-1, ref work);
+                        }
+                    }
+                }
+                
+                //
+                // Advance
+                //
+                blockstart = blockstart-ablas.ablasblocksize(a);
+                blocksize = ablas.ablasblocksize(a);
+            }
+        }
+
+
+        /*************************************************************************
+        Unpacking of matrix L from the LQ decomposition of a matrix A
+
+        Input parameters:
+            A       -   matrices Q and L in compact form.
+                        Output of RMatrixLQ subroutine.
+            M       -   number of rows in given matrix A. M>=0.
+            N       -   number of columns in given matrix A. N>=0.
+
+        Output parameters:
+            L       -   matrix L, array[0..M-1, 0..N-1].
+
+          -- ALGLIB routine --
+             17.02.2010
+             Bochkanov Sergey
+        *************************************************************************/
+        public static void rmatrixlqunpackl(double[,] a,
+            int m,
+            int n,
+            ref double[,] l)
+        {
+            int i = 0;
+            int k = 0;
+            int i_ = 0;
+
+            l = new double[0,0];
+
+            if( m<=0 || n<=0 )
+            {
+                return;
+            }
+            l = new double[m, n];
+            for(i=0; i<=n-1; i++)
+            {
+                l[0,i] = 0;
+            }
+            for(i=1; i<=m-1; i++)
+            {
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    l[i,i_] = l[0,i_];
+                }
+            }
+            for(i=0; i<=m-1; i++)
+            {
+                k = Math.Min(i, n-1);
+                for(i_=0; i_<=k;i_++)
+                {
+                    l[i,i_] = a[i,i_];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Partial unpacking of matrix Q from QR decomposition of a complex matrix A.
+
+        Input parameters:
+            A           -   matrices Q and R in compact form.
+                            Output of CMatrixQR subroutine .
+            M           -   number of rows in matrix A. M>=0.
+            N           -   number of columns in matrix A. N>=0.
+            Tau         -   scalar factors which are used to form Q.
+                            Output of CMatrixQR subroutine .
+            QColumns    -   required number of columns in matrix Q. M>=QColumns>=0.
+
+        Output parameters:
+            Q           -   first QColumns columns of matrix Q.
+                            Array whose index ranges within [0..M-1, 0..QColumns-1].
+                            If QColumns=0, array isn't changed.
+
+          -- ALGLIB routine --
+             17.02.2010
+             Bochkanov Sergey
+        *************************************************************************/
+        public static void cmatrixqrunpackq(complex[,] a,
+            int m,
+            int n,
+            complex[] tau,
+            int qcolumns,
+            ref complex[,] q)
+        {
+            complex[] work = new complex[0];
+            complex[] t = new complex[0];
+            complex[] taubuf = new complex[0];
+            int minmn = 0;
+            int refcnt = 0;
+            complex[,] tmpa = new complex[0,0];
+            complex[,] tmpt = new complex[0,0];
+            complex[,] tmpr = new complex[0,0];
+            int blockstart = 0;
+            int blocksize = 0;
+            int rowscount = 0;
+            int i = 0;
+            int j = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            q = new complex[0,0];
+
+            alglib.ap.assert(qcolumns<=m, "UnpackQFromQR: QColumns>M!");
+            if( m<=0 || n<=0 )
+            {
+                return;
+            }
+            
+            //
+            // init
+            //
+            minmn = Math.Min(m, n);
+            refcnt = Math.Min(minmn, qcolumns);
+            work = new complex[Math.Max(m, n)+1];
+            t = new complex[Math.Max(m, n)+1];
+            taubuf = new complex[minmn];
+            tmpa = new complex[m, ablas.ablascomplexblocksize(a)];
+            tmpt = new complex[ablas.ablascomplexblocksize(a), ablas.ablascomplexblocksize(a)];
+            tmpr = new complex[2*ablas.ablascomplexblocksize(a), qcolumns];
+            q = new complex[m, qcolumns];
+            for(i=0; i<=m-1; i++)
+            {
+                for(j=0; j<=qcolumns-1; j++)
+                {
+                    if( i==j )
+                    {
+                        q[i,j] = 1;
+                    }
+                    else
+                    {
+                        q[i,j] = 0;
+                    }
+                }
+            }
+            
+            //
+            // Blocked code
+            //
+            blockstart = ablas.ablascomplexblocksize(a)*(refcnt/ablas.ablascomplexblocksize(a));
+            blocksize = refcnt-blockstart;
+            while( blockstart>=0 )
+            {
+                rowscount = m-blockstart;
+                if( blocksize>0 )
+                {
+                    
+                    //
+                    // QR decomposition of submatrix.
+                    // Matrix is copied to temporary storage to solve
+                    // some TLB issues arising from non-contiguous memory
+                    // access pattern.
+                    //
+                    ablas.cmatrixcopy(rowscount, blocksize, a, blockstart, blockstart, ref tmpa, 0, 0);
+                    i1_ = (blockstart) - (0);
+                    for(i_=0; i_<=blocksize-1;i_++)
+                    {
+                        taubuf[i_] = tau[i_+i1_];
+                    }
+                    
+                    //
+                    // Update matrix, choose between:
+                    // a) Level 2 algorithm (when the rest of the matrix is small enough)
+                    // b) blocked algorithm, see algorithm 5 from  'A storage efficient WY
+                    //    representation for products of Householder transformations',
+                    //    by R. Schreiber and C. Van Loan.
+                    //
+                    if( qcolumns>=2*ablas.ablascomplexblocksize(a) )
+                    {
+                        
+                        //
+                        // Prepare block reflector
+                        //
+                        cmatrixblockreflector(ref tmpa, ref taubuf, true, rowscount, blocksize, ref tmpt, ref work);
+                        
+                        //
+                        // Multiply the rest of A by Q.
+                        //
+                        // Q  = E + Y*T*Y'  = E + TmpA*TmpT*TmpA'
+                        //
+                        ablas.cmatrixgemm(blocksize, qcolumns, rowscount, 1.0, tmpa, 0, 0, 2, q, blockstart, 0, 0, 0.0, ref tmpr, 0, 0);
+                        ablas.cmatrixgemm(blocksize, qcolumns, blocksize, 1.0, tmpt, 0, 0, 0, tmpr, 0, 0, 0, 0.0, ref tmpr, blocksize, 0);
+                        ablas.cmatrixgemm(rowscount, qcolumns, blocksize, 1.0, tmpa, 0, 0, 0, tmpr, blocksize, 0, 0, 1.0, ref q, blockstart, 0);
+                    }
+                    else
+                    {
+                        
+                        //
+                        // Level 2 algorithm
+                        //
+                        for(i=blocksize-1; i>=0; i--)
+                        {
+                            i1_ = (i) - (1);
+                            for(i_=1; i_<=rowscount-i;i_++)
+                            {
+                                t[i_] = tmpa[i_+i1_,i];
+                            }
+                            t[1] = 1;
+                            creflections.complexapplyreflectionfromtheleft(ref q, taubuf[i], t, blockstart+i, m-1, 0, qcolumns-1, ref work);
+                        }
+                    }
+                }
+                
+                //
+                // Advance
+                //
+                blockstart = blockstart-ablas.ablascomplexblocksize(a);
+                blocksize = ablas.ablascomplexblocksize(a);
+            }
+        }
+
+
+        /*************************************************************************
+        Unpacking of matrix R from the QR decomposition of a matrix A
+
+        Input parameters:
+            A       -   matrices Q and R in compact form.
+                        Output of CMatrixQR subroutine.
+            M       -   number of rows in given matrix A. M>=0.
+            N       -   number of columns in given matrix A. N>=0.
+
+        Output parameters:
+            R       -   matrix R, array[0..M-1, 0..N-1].
+
+          -- ALGLIB routine --
+             17.02.2010
+             Bochkanov Sergey
+        *************************************************************************/
+        public static void cmatrixqrunpackr(complex[,] a,
+            int m,
+            int n,
+            ref complex[,] r)
+        {
+            int i = 0;
+            int k = 0;
+            int i_ = 0;
+
+            r = new complex[0,0];
+
+            if( m<=0 || n<=0 )
+            {
+                return;
+            }
+            k = Math.Min(m, n);
+            r = new complex[m, n];
+            for(i=0; i<=n-1; i++)
+            {
+                r[0,i] = 0;
+            }
+            for(i=1; i<=m-1; i++)
+            {
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    r[i,i_] = r[0,i_];
+                }
+            }
+            for(i=0; i<=k-1; i++)
+            {
+                for(i_=i; i_<=n-1;i_++)
+                {
+                    r[i,i_] = a[i,i_];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Partial unpacking of matrix Q from LQ decomposition of a complex matrix A.
+
+        Input parameters:
+            A           -   matrices Q and R in compact form.
+                            Output of CMatrixLQ subroutine .
+            M           -   number of rows in matrix A. M>=0.
+            N           -   number of columns in matrix A. N>=0.
+            Tau         -   scalar factors which are used to form Q.
+                            Output of CMatrixLQ subroutine .
+            QRows       -   required number of rows in matrix Q. N>=QColumns>=0.
+
+        Output parameters:
+            Q           -   first QRows rows of matrix Q.
+                            Array whose index ranges within [0..QRows-1, 0..N-1].
+                            If QRows=0, array isn't changed.
+
+          -- ALGLIB routine --
+             17.02.2010
+             Bochkanov Sergey
+        *************************************************************************/
+        public static void cmatrixlqunpackq(complex[,] a,
+            int m,
+            int n,
+            complex[] tau,
+            int qrows,
+            ref complex[,] q)
+        {
+            complex[] work = new complex[0];
+            complex[] t = new complex[0];
+            complex[] taubuf = new complex[0];
+            int minmn = 0;
+            int refcnt = 0;
+            complex[,] tmpa = new complex[0,0];
+            complex[,] tmpt = new complex[0,0];
+            complex[,] tmpr = new complex[0,0];
+            int blockstart = 0;
+            int blocksize = 0;
+            int columnscount = 0;
+            int i = 0;
+            int j = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            q = new complex[0,0];
+
+            if( m<=0 || n<=0 )
+            {
+                return;
+            }
+            
+            //
+            // Init
+            //
+            minmn = Math.Min(m, n);
+            refcnt = Math.Min(minmn, qrows);
+            work = new complex[Math.Max(m, n)+1];
+            t = new complex[Math.Max(m, n)+1];
+            taubuf = new complex[minmn];
+            tmpa = new complex[ablas.ablascomplexblocksize(a), n];
+            tmpt = new complex[ablas.ablascomplexblocksize(a), ablas.ablascomplexblocksize(a)];
+            tmpr = new complex[qrows, 2*ablas.ablascomplexblocksize(a)];
+            q = new complex[qrows, n];
+            for(i=0; i<=qrows-1; i++)
+            {
+                for(j=0; j<=n-1; j++)
+                {
+                    if( i==j )
+                    {
+                        q[i,j] = 1;
+                    }
+                    else
+                    {
+                        q[i,j] = 0;
+                    }
+                }
+            }
+            
+            //
+            // Blocked code
+            //
+            blockstart = ablas.ablascomplexblocksize(a)*(refcnt/ablas.ablascomplexblocksize(a));
+            blocksize = refcnt-blockstart;
+            while( blockstart>=0 )
+            {
+                columnscount = n-blockstart;
+                if( blocksize>0 )
+                {
+                    
+                    //
+                    // LQ decomposition of submatrix.
+                    // Matrix is copied to temporary storage to solve
+                    // some TLB issues arising from non-contiguous memory
+                    // access pattern.
+                    //
+                    ablas.cmatrixcopy(blocksize, columnscount, a, blockstart, blockstart, ref tmpa, 0, 0);
+                    i1_ = (blockstart) - (0);
+                    for(i_=0; i_<=blocksize-1;i_++)
+                    {
+                        taubuf[i_] = tau[i_+i1_];
+                    }
+                    
+                    //
+                    // Update matrix, choose between:
+                    // a) Level 2 algorithm (when the rest of the matrix is small enough)
+                    // b) blocked algorithm, see algorithm 5 from  'A storage efficient WY
+                    //    representation for products of Householder transformations',
+                    //    by R. Schreiber and C. Van Loan.
+                    //
+                    if( qrows>=2*ablas.ablascomplexblocksize(a) )
+                    {
+                        
+                        //
+                        // Prepare block reflector
+                        //
+                        cmatrixblockreflector(ref tmpa, ref taubuf, false, columnscount, blocksize, ref tmpt, ref work);
+                        
+                        //
+                        // Multiply the rest of A by Q'.
+                        //
+                        // Q'  = E + Y*T'*Y'  = E + TmpA'*TmpT'*TmpA
+                        //
+                        ablas.cmatrixgemm(qrows, blocksize, columnscount, 1.0, q, 0, blockstart, 0, tmpa, 0, 0, 2, 0.0, ref tmpr, 0, 0);
+                        ablas.cmatrixgemm(qrows, blocksize, blocksize, 1.0, tmpr, 0, 0, 0, tmpt, 0, 0, 2, 0.0, ref tmpr, 0, blocksize);
+                        ablas.cmatrixgemm(qrows, columnscount, blocksize, 1.0, tmpr, 0, blocksize, 0, tmpa, 0, 0, 0, 1.0, ref q, 0, blockstart);
+                    }
+                    else
+                    {
+                        
+                        //
+                        // Level 2 algorithm
+                        //
+                        for(i=blocksize-1; i>=0; i--)
+                        {
+                            i1_ = (i) - (1);
+                            for(i_=1; i_<=columnscount-i;i_++)
+                            {
+                                t[i_] = math.conj(tmpa[i,i_+i1_]);
+                            }
+                            t[1] = 1;
+                            creflections.complexapplyreflectionfromtheright(ref q, math.conj(taubuf[i]), ref t, 0, qrows-1, blockstart+i, n-1, ref work);
+                        }
+                    }
+                }
+                
+                //
+                // Advance
+                //
+                blockstart = blockstart-ablas.ablascomplexblocksize(a);
+                blocksize = ablas.ablascomplexblocksize(a);
+            }
+        }
+
+
+        /*************************************************************************
+        Unpacking of matrix L from the LQ decomposition of a matrix A
+
+        Input parameters:
+            A       -   matrices Q and L in compact form.
+                        Output of CMatrixLQ subroutine.
+            M       -   number of rows in given matrix A. M>=0.
+            N       -   number of columns in given matrix A. N>=0.
+
+        Output parameters:
+            L       -   matrix L, array[0..M-1, 0..N-1].
+
+          -- ALGLIB routine --
+             17.02.2010
+             Bochkanov Sergey
+        *************************************************************************/
+        public static void cmatrixlqunpackl(complex[,] a,
+            int m,
+            int n,
+            ref complex[,] l)
+        {
+            int i = 0;
+            int k = 0;
+            int i_ = 0;
+
+            l = new complex[0,0];
+
+            if( m<=0 || n<=0 )
+            {
+                return;
+            }
+            l = new complex[m, n];
+            for(i=0; i<=n-1; i++)
+            {
+                l[0,i] = 0;
+            }
+            for(i=1; i<=m-1; i++)
+            {
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    l[i,i_] = l[0,i_];
+                }
+            }
+            for(i=0; i<=m-1; i++)
+            {
+                k = Math.Min(i, n-1);
+                for(i_=0; i_<=k;i_++)
+                {
+                    l[i,i_] = a[i,i_];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Base case for real QR
+
+          -- LAPACK routine (version 3.0) --
+             Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd.,
+             Courant Institute, Argonne National Lab, and Rice University
+             September 30, 1994.
+             Sergey Bochkanov, ALGLIB project, translation from FORTRAN to
+             pseudocode, 2007-2010.
+        *************************************************************************/
+        public static void rmatrixqrbasecase(ref double[,] a,
+            int m,
+            int n,
+            ref double[] work,
+            ref double[] t,
+            ref double[] tau)
+        {
+            int i = 0;
+            int k = 0;
+            int minmn = 0;
+            double tmp = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            minmn = Math.Min(m, n);
+            
+            //
+            // Test the input arguments
+            //
+            k = minmn;
+            for(i=0; i<=k-1; i++)
+            {
+                
+                //
+                // Generate elementary reflector H(i) to annihilate A(i+1:m,i)
+                //
+                i1_ = (i) - (1);
+                for(i_=1; i_<=m-i;i_++)
+                {
+                    t[i_] = a[i_+i1_,i];
+                }
+                reflections.generatereflection(ref t, m-i, ref tmp);
+                tau[i] = tmp;
+                i1_ = (1) - (i);
+                for(i_=i; i_<=m-1;i_++)
+                {
+                    a[i_,i] = t[i_+i1_];
+                }
+                t[1] = 1;
+                if( i<n )
+                {
+                    
+                    //
+                    // Apply H(i) to A(i:m-1,i+1:n-1) from the left
+                    //
+                    reflections.applyreflectionfromtheleft(ref a, tau[i], t, i, m-1, i+1, n-1, ref work);
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Base case for real LQ
+
+          -- LAPACK routine (version 3.0) --
+             Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd.,
+             Courant Institute, Argonne National Lab, and Rice University
+             September 30, 1994.
+             Sergey Bochkanov, ALGLIB project, translation from FORTRAN to
+             pseudocode, 2007-2010.
+        *************************************************************************/
+        public static void rmatrixlqbasecase(ref double[,] a,
+            int m,
+            int n,
+            ref double[] work,
+            ref double[] t,
+            ref double[] tau)
+        {
+            int i = 0;
+            int k = 0;
+            int minmn = 0;
+            double tmp = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            minmn = Math.Min(m, n);
+            k = Math.Min(m, n);
+            for(i=0; i<=k-1; i++)
+            {
+                
+                //
+                // Generate elementary reflector H(i) to annihilate A(i,i+1:n-1)
+                //
+                i1_ = (i) - (1);
+                for(i_=1; i_<=n-i;i_++)
+                {
+                    t[i_] = a[i,i_+i1_];
+                }
+                reflections.generatereflection(ref t, n-i, ref tmp);
+                tau[i] = tmp;
+                i1_ = (1) - (i);
+                for(i_=i; i_<=n-1;i_++)
+                {
+                    a[i,i_] = t[i_+i1_];
+                }
+                t[1] = 1;
+                if( i<n )
+                {
+                    
+                    //
+                    // Apply H(i) to A(i+1:m,i:n) from the right
+                    //
+                    reflections.applyreflectionfromtheright(ref a, tau[i], t, i+1, m-1, i, n-1, ref work);
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Reduction of a rectangular matrix to  bidiagonal form
+
+        The algorithm reduces the rectangular matrix A to  bidiagonal form by
+        orthogonal transformations P and Q: A = Q*B*P.
+
+        Input parameters:
+            A       -   source matrix. array[0..M-1, 0..N-1]
+            M       -   number of rows in matrix A.
+            N       -   number of columns in matrix A.
+
+        Output parameters:
+            A       -   matrices Q, B, P in compact form (see below).
+            TauQ    -   scalar factors which are used to form matrix Q.
+            TauP    -   scalar factors which are used to form matrix P.
+
+        The main diagonal and one of the  secondary  diagonals  of  matrix  A  are
+        replaced with bidiagonal  matrix  B.  Other  elements  contain  elementary
+        reflections which form MxM matrix Q and NxN matrix P, respectively.
+
+        If M>=N, B is the upper  bidiagonal  MxN  matrix  and  is  stored  in  the
+        corresponding  elements  of  matrix  A.  Matrix  Q  is  represented  as  a
+        product   of   elementary   reflections   Q = H(0)*H(1)*...*H(n-1),  where
+        H(i) = 1-tau*v*v'. Here tau is a scalar which is stored  in  TauQ[i],  and
+        vector v has the following  structure:  v(0:i-1)=0, v(i)=1, v(i+1:m-1)  is
+        stored   in   elements   A(i+1:m-1,i).   Matrix   P  is  as  follows:  P =
+        G(0)*G(1)*...*G(n-2), where G(i) = 1 - tau*u*u'. Tau is stored in TauP[i],
+        u(0:i)=0, u(i+1)=1, u(i+2:n-1) is stored in elements A(i,i+2:n-1).
+
+        If M<N, B is the  lower  bidiagonal  MxN  matrix  and  is  stored  in  the
+        corresponding   elements  of  matrix  A.  Q = H(0)*H(1)*...*H(m-2),  where
+        H(i) = 1 - tau*v*v', tau is stored in TauQ, v(0:i)=0, v(i+1)=1, v(i+2:m-1)
+        is    stored    in   elements   A(i+2:m-1,i).    P = G(0)*G(1)*...*G(m-1),
+        G(i) = 1-tau*u*u', tau is stored in  TauP,  u(0:i-1)=0, u(i)=1, u(i+1:n-1)
+        is stored in A(i,i+1:n-1).
+
+        EXAMPLE:
+
+        m=6, n=5 (m > n):               m=5, n=6 (m < n):
+
+        (  d   e   u1  u1  u1 )         (  d   u1  u1  u1  u1  u1 )
+        (  v1  d   e   u2  u2 )         (  e   d   u2  u2  u2  u2 )
+        (  v1  v2  d   e   u3 )         (  v1  e   d   u3  u3  u3 )
+        (  v1  v2  v3  d   e  )         (  v1  v2  e   d   u4  u4 )
+        (  v1  v2  v3  v4  d  )         (  v1  v2  v3  e   d   u5 )
+        (  v1  v2  v3  v4  v5 )
+
+        Here vi and ui are vectors which form H(i) and G(i), and d and e -
+        are the diagonal and off-diagonal elements of matrix B.
+
+          -- LAPACK routine (version 3.0) --
+             Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd.,
+             Courant Institute, Argonne National Lab, and Rice University
+             September 30, 1994.
+             Sergey Bochkanov, ALGLIB project, translation from FORTRAN to
+             pseudocode, 2007-2010.
+        *************************************************************************/
+        public static void rmatrixbd(ref double[,] a,
+            int m,
+            int n,
+            ref double[] tauq,
+            ref double[] taup)
+        {
+            double[] work = new double[0];
+            double[] t = new double[0];
+            int minmn = 0;
+            int maxmn = 0;
+            int i = 0;
+            double ltau = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            tauq = new double[0];
+            taup = new double[0];
+
+            
+            //
+            // Prepare
+            //
+            if( n<=0 || m<=0 )
+            {
+                return;
+            }
+            minmn = Math.Min(m, n);
+            maxmn = Math.Max(m, n);
+            work = new double[maxmn+1];
+            t = new double[maxmn+1];
+            if( m>=n )
+            {
+                tauq = new double[n];
+                taup = new double[n];
+            }
+            else
+            {
+                tauq = new double[m];
+                taup = new double[m];
+            }
+            if( m>=n )
+            {
+                
+                //
+                // Reduce to upper bidiagonal form
+                //
+                for(i=0; i<=n-1; i++)
+                {
+                    
+                    //
+                    // Generate elementary reflector H(i) to annihilate A(i+1:m-1,i)
+                    //
+                    i1_ = (i) - (1);
+                    for(i_=1; i_<=m-i;i_++)
+                    {
+                        t[i_] = a[i_+i1_,i];
+                    }
+                    reflections.generatereflection(ref t, m-i, ref ltau);
+                    tauq[i] = ltau;
+                    i1_ = (1) - (i);
+                    for(i_=i; i_<=m-1;i_++)
+                    {
+                        a[i_,i] = t[i_+i1_];
+                    }
+                    t[1] = 1;
+                    
+                    //
+                    // Apply H(i) to A(i:m-1,i+1:n-1) from the left
+                    //
+                    reflections.applyreflectionfromtheleft(ref a, ltau, t, i, m-1, i+1, n-1, ref work);
+                    if( i<n-1 )
+                    {
+                        
+                        //
+                        // Generate elementary reflector G(i) to annihilate
+                        // A(i,i+2:n-1)
+                        //
+                        i1_ = (i+1) - (1);
+                        for(i_=1; i_<=n-i-1;i_++)
+                        {
+                            t[i_] = a[i,i_+i1_];
+                        }
+                        reflections.generatereflection(ref t, n-1-i, ref ltau);
+                        taup[i] = ltau;
+                        i1_ = (1) - (i+1);
+                        for(i_=i+1; i_<=n-1;i_++)
+                        {
+                            a[i,i_] = t[i_+i1_];
+                        }
+                        t[1] = 1;
+                        
+                        //
+                        // Apply G(i) to A(i+1:m-1,i+1:n-1) from the right
+                        //
+                        reflections.applyreflectionfromtheright(ref a, ltau, t, i+1, m-1, i+1, n-1, ref work);
+                    }
+                    else
+                    {
+                        taup[i] = 0;
+                    }
+                }
+            }
+            else
+            {
+                
+                //
+                // Reduce to lower bidiagonal form
+                //
+                for(i=0; i<=m-1; i++)
+                {
+                    
+                    //
+                    // Generate elementary reflector G(i) to annihilate A(i,i+1:n-1)
+                    //
+                    i1_ = (i) - (1);
+                    for(i_=1; i_<=n-i;i_++)
+                    {
+                        t[i_] = a[i,i_+i1_];
+                    }
+                    reflections.generatereflection(ref t, n-i, ref ltau);
+                    taup[i] = ltau;
+                    i1_ = (1) - (i);
+                    for(i_=i; i_<=n-1;i_++)
+                    {
+                        a[i,i_] = t[i_+i1_];
+                    }
+                    t[1] = 1;
+                    
+                    //
+                    // Apply G(i) to A(i+1:m-1,i:n-1) from the right
+                    //
+                    reflections.applyreflectionfromtheright(ref a, ltau, t, i+1, m-1, i, n-1, ref work);
+                    if( i<m-1 )
+                    {
+                        
+                        //
+                        // Generate elementary reflector H(i) to annihilate
+                        // A(i+2:m-1,i)
+                        //
+                        i1_ = (i+1) - (1);
+                        for(i_=1; i_<=m-1-i;i_++)
+                        {
+                            t[i_] = a[i_+i1_,i];
+                        }
+                        reflections.generatereflection(ref t, m-1-i, ref ltau);
+                        tauq[i] = ltau;
+                        i1_ = (1) - (i+1);
+                        for(i_=i+1; i_<=m-1;i_++)
+                        {
+                            a[i_,i] = t[i_+i1_];
+                        }
+                        t[1] = 1;
+                        
+                        //
+                        // Apply H(i) to A(i+1:m-1,i+1:n-1) from the left
+                        //
+                        reflections.applyreflectionfromtheleft(ref a, ltau, t, i+1, m-1, i+1, n-1, ref work);
+                    }
+                    else
+                    {
+                        tauq[i] = 0;
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Unpacking matrix Q which reduces a matrix to bidiagonal form.
+
+        Input parameters:
+            QP          -   matrices Q and P in compact form.
+                            Output of ToBidiagonal subroutine.
+            M           -   number of rows in matrix A.
+            N           -   number of columns in matrix A.
+            TAUQ        -   scalar factors which are used to form Q.
+                            Output of ToBidiagonal subroutine.
+            QColumns    -   required number of columns in matrix Q.
+                            M>=QColumns>=0.
+
+        Output parameters:
+            Q           -   first QColumns columns of matrix Q.
+                            Array[0..M-1, 0..QColumns-1]
+                            If QColumns=0, the array is not modified.
+
+          -- ALGLIB --
+             2005-2010
+             Bochkanov Sergey
+        *************************************************************************/
+        public static void rmatrixbdunpackq(double[,] qp,
+            int m,
+            int n,
+            double[] tauq,
+            int qcolumns,
+            ref double[,] q)
+        {
+            int i = 0;
+            int j = 0;
+
+            q = new double[0,0];
+
+            alglib.ap.assert(qcolumns<=m, "RMatrixBDUnpackQ: QColumns>M!");
+            alglib.ap.assert(qcolumns>=0, "RMatrixBDUnpackQ: QColumns<0!");
+            if( (m==0 || n==0) || qcolumns==0 )
+            {
+                return;
+            }
+            
+            //
+            // prepare Q
+            //
+            q = new double[m, qcolumns];
+            for(i=0; i<=m-1; i++)
+            {
+                for(j=0; j<=qcolumns-1; j++)
+                {
+                    if( i==j )
+                    {
+                        q[i,j] = 1;
+                    }
+                    else
+                    {
+                        q[i,j] = 0;
+                    }
+                }
+            }
+            
+            //
+            // Calculate
+            //
+            rmatrixbdmultiplybyq(qp, m, n, tauq, ref q, m, qcolumns, false, false);
+        }
+
+
+        /*************************************************************************
+        Multiplication by matrix Q which reduces matrix A to  bidiagonal form.
+
+        The algorithm allows pre- or post-multiply by Q or Q'.
+
+        Input parameters:
+            QP          -   matrices Q and P in compact form.
+                            Output of ToBidiagonal subroutine.
+            M           -   number of rows in matrix A.
+            N           -   number of columns in matrix A.
+            TAUQ        -   scalar factors which are used to form Q.
+                            Output of ToBidiagonal subroutine.
+            Z           -   multiplied matrix.
+                            array[0..ZRows-1,0..ZColumns-1]
+            ZRows       -   number of rows in matrix Z. If FromTheRight=False,
+                            ZRows=M, otherwise ZRows can be arbitrary.
+            ZColumns    -   number of columns in matrix Z. If FromTheRight=True,
+                            ZColumns=M, otherwise ZColumns can be arbitrary.
+            FromTheRight -  pre- or post-multiply.
+            DoTranspose -   multiply by Q or Q'.
+
+        Output parameters:
+            Z           -   product of Z and Q.
+                            Array[0..ZRows-1,0..ZColumns-1]
+                            If ZRows=0 or ZColumns=0, the array is not modified.
+
+          -- ALGLIB --
+             2005-2010
+             Bochkanov Sergey
+        *************************************************************************/
+        public static void rmatrixbdmultiplybyq(double[,] qp,
+            int m,
+            int n,
+            double[] tauq,
+            ref double[,] z,
+            int zrows,
+            int zcolumns,
+            bool fromtheright,
+            bool dotranspose)
+        {
+            int i = 0;
+            int i1 = 0;
+            int i2 = 0;
+            int istep = 0;
+            double[] v = new double[0];
+            double[] work = new double[0];
+            int mx = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            if( ((m<=0 || n<=0) || zrows<=0) || zcolumns<=0 )
+            {
+                return;
+            }
+            alglib.ap.assert((fromtheright && zcolumns==m) || (!fromtheright && zrows==m), "RMatrixBDMultiplyByQ: incorrect Z size!");
+            
+            //
+            // init
+            //
+            mx = Math.Max(m, n);
+            mx = Math.Max(mx, zrows);
+            mx = Math.Max(mx, zcolumns);
+            v = new double[mx+1];
+            work = new double[mx+1];
+            if( m>=n )
+            {
+                
+                //
+                // setup
+                //
+                if( fromtheright )
+                {
+                    i1 = 0;
+                    i2 = n-1;
+                    istep = 1;
+                }
+                else
+                {
+                    i1 = n-1;
+                    i2 = 0;
+                    istep = -1;
+                }
+                if( dotranspose )
+                {
+                    i = i1;
+                    i1 = i2;
+                    i2 = i;
+                    istep = -istep;
+                }
+                
+                //
+                // Process
+                //
+                i = i1;
+                do
+                {
+                    i1_ = (i) - (1);
+                    for(i_=1; i_<=m-i;i_++)
+                    {
+                        v[i_] = qp[i_+i1_,i];
+                    }
+                    v[1] = 1;
+                    if( fromtheright )
+                    {
+                        reflections.applyreflectionfromtheright(ref z, tauq[i], v, 0, zrows-1, i, m-1, ref work);
+                    }
+                    else
+                    {
+                        reflections.applyreflectionfromtheleft(ref z, tauq[i], v, i, m-1, 0, zcolumns-1, ref work);
+                    }
+                    i = i+istep;
+                }
+                while( i!=i2+istep );
+            }
+            else
+            {
+                
+                //
+                // setup
+                //
+                if( fromtheright )
+                {
+                    i1 = 0;
+                    i2 = m-2;
+                    istep = 1;
+                }
+                else
+                {
+                    i1 = m-2;
+                    i2 = 0;
+                    istep = -1;
+                }
+                if( dotranspose )
+                {
+                    i = i1;
+                    i1 = i2;
+                    i2 = i;
+                    istep = -istep;
+                }
+                
+                //
+                // Process
+                //
+                if( m-1>0 )
+                {
+                    i = i1;
+                    do
+                    {
+                        i1_ = (i+1) - (1);
+                        for(i_=1; i_<=m-i-1;i_++)
+                        {
+                            v[i_] = qp[i_+i1_,i];
+                        }
+                        v[1] = 1;
+                        if( fromtheright )
+                        {
+                            reflections.applyreflectionfromtheright(ref z, tauq[i], v, 0, zrows-1, i+1, m-1, ref work);
+                        }
+                        else
+                        {
+                            reflections.applyreflectionfromtheleft(ref z, tauq[i], v, i+1, m-1, 0, zcolumns-1, ref work);
+                        }
+                        i = i+istep;
+                    }
+                    while( i!=i2+istep );
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Unpacking matrix P which reduces matrix A to bidiagonal form.
+        The subroutine returns transposed matrix P.
+
+        Input parameters:
+            QP      -   matrices Q and P in compact form.
+                        Output of ToBidiagonal subroutine.
+            M       -   number of rows in matrix A.
+            N       -   number of columns in matrix A.
+            TAUP    -   scalar factors which are used to form P.
+                        Output of ToBidiagonal subroutine.
+            PTRows  -   required number of rows of matrix P^T. N >= PTRows >= 0.
+
+        Output parameters:
+            PT      -   first PTRows columns of matrix P^T
+                        Array[0..PTRows-1, 0..N-1]
+                        If PTRows=0, the array is not modified.
+
+          -- ALGLIB --
+             2005-2010
+             Bochkanov Sergey
+        *************************************************************************/
+        public static void rmatrixbdunpackpt(double[,] qp,
+            int m,
+            int n,
+            double[] taup,
+            int ptrows,
+            ref double[,] pt)
+        {
+            int i = 0;
+            int j = 0;
+
+            pt = new double[0,0];
+
+            alglib.ap.assert(ptrows<=n, "RMatrixBDUnpackPT: PTRows>N!");
+            alglib.ap.assert(ptrows>=0, "RMatrixBDUnpackPT: PTRows<0!");
+            if( (m==0 || n==0) || ptrows==0 )
+            {
+                return;
+            }
+            
+            //
+            // prepare PT
+            //
+            pt = new double[ptrows, n];
+            for(i=0; i<=ptrows-1; i++)
+            {
+                for(j=0; j<=n-1; j++)
+                {
+                    if( i==j )
+                    {
+                        pt[i,j] = 1;
+                    }
+                    else
+                    {
+                        pt[i,j] = 0;
+                    }
+                }
+            }
+            
+            //
+            // Calculate
+            //
+            rmatrixbdmultiplybyp(qp, m, n, taup, ref pt, ptrows, n, true, true);
+        }
+
+
+        /*************************************************************************
+        Multiplication by matrix P which reduces matrix A to  bidiagonal form.
+
+        The algorithm allows pre- or post-multiply by P or P'.
+
+        Input parameters:
+            QP          -   matrices Q and P in compact form.
+                            Output of RMatrixBD subroutine.
+            M           -   number of rows in matrix A.
+            N           -   number of columns in matrix A.
+            TAUP        -   scalar factors which are used to form P.
+                            Output of RMatrixBD subroutine.
+            Z           -   multiplied matrix.
+                            Array whose indexes range within [0..ZRows-1,0..ZColumns-1].
+            ZRows       -   number of rows in matrix Z. If FromTheRight=False,
+                            ZRows=N, otherwise ZRows can be arbitrary.
+            ZColumns    -   number of columns in matrix Z. If FromTheRight=True,
+                            ZColumns=N, otherwise ZColumns can be arbitrary.
+            FromTheRight -  pre- or post-multiply.
+            DoTranspose -   multiply by P or P'.
+
+        Output parameters:
+            Z - product of Z and P.
+                        Array whose indexes range within [0..ZRows-1,0..ZColumns-1].
+                        If ZRows=0 or ZColumns=0, the array is not modified.
+
+          -- ALGLIB --
+             2005-2010
+             Bochkanov Sergey
+        *************************************************************************/
+        public static void rmatrixbdmultiplybyp(double[,] qp,
+            int m,
+            int n,
+            double[] taup,
+            ref double[,] z,
+            int zrows,
+            int zcolumns,
+            bool fromtheright,
+            bool dotranspose)
+        {
+            int i = 0;
+            double[] v = new double[0];
+            double[] work = new double[0];
+            int mx = 0;
+            int i1 = 0;
+            int i2 = 0;
+            int istep = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            if( ((m<=0 || n<=0) || zrows<=0) || zcolumns<=0 )
+            {
+                return;
+            }
+            alglib.ap.assert((fromtheright && zcolumns==n) || (!fromtheright && zrows==n), "RMatrixBDMultiplyByP: incorrect Z size!");
+            
+            //
+            // init
+            //
+            mx = Math.Max(m, n);
+            mx = Math.Max(mx, zrows);
+            mx = Math.Max(mx, zcolumns);
+            v = new double[mx+1];
+            work = new double[mx+1];
+            if( m>=n )
+            {
+                
+                //
+                // setup
+                //
+                if( fromtheright )
+                {
+                    i1 = n-2;
+                    i2 = 0;
+                    istep = -1;
+                }
+                else
+                {
+                    i1 = 0;
+                    i2 = n-2;
+                    istep = 1;
+                }
+                if( !dotranspose )
+                {
+                    i = i1;
+                    i1 = i2;
+                    i2 = i;
+                    istep = -istep;
+                }
+                
+                //
+                // Process
+                //
+                if( n-1>0 )
+                {
+                    i = i1;
+                    do
+                    {
+                        i1_ = (i+1) - (1);
+                        for(i_=1; i_<=n-1-i;i_++)
+                        {
+                            v[i_] = qp[i,i_+i1_];
+                        }
+                        v[1] = 1;
+                        if( fromtheright )
+                        {
+                            reflections.applyreflectionfromtheright(ref z, taup[i], v, 0, zrows-1, i+1, n-1, ref work);
+                        }
+                        else
+                        {
+                            reflections.applyreflectionfromtheleft(ref z, taup[i], v, i+1, n-1, 0, zcolumns-1, ref work);
+                        }
+                        i = i+istep;
+                    }
+                    while( i!=i2+istep );
+                }
+            }
+            else
+            {
+                
+                //
+                // setup
+                //
+                if( fromtheright )
+                {
+                    i1 = m-1;
+                    i2 = 0;
+                    istep = -1;
+                }
+                else
+                {
+                    i1 = 0;
+                    i2 = m-1;
+                    istep = 1;
+                }
+                if( !dotranspose )
+                {
+                    i = i1;
+                    i1 = i2;
+                    i2 = i;
+                    istep = -istep;
+                }
+                
+                //
+                // Process
+                //
+                i = i1;
+                do
+                {
+                    i1_ = (i) - (1);
+                    for(i_=1; i_<=n-i;i_++)
+                    {
+                        v[i_] = qp[i,i_+i1_];
+                    }
+                    v[1] = 1;
+                    if( fromtheright )
+                    {
+                        reflections.applyreflectionfromtheright(ref z, taup[i], v, 0, zrows-1, i, n-1, ref work);
+                    }
+                    else
+                    {
+                        reflections.applyreflectionfromtheleft(ref z, taup[i], v, i, n-1, 0, zcolumns-1, ref work);
+                    }
+                    i = i+istep;
+                }
+                while( i!=i2+istep );
+            }
+        }
+
+
+        /*************************************************************************
+        Unpacking of the main and secondary diagonals of bidiagonal decomposition
+        of matrix A.
+
+        Input parameters:
+            B   -   output of RMatrixBD subroutine.
+            M   -   number of rows in matrix B.
+            N   -   number of columns in matrix B.
+
+        Output parameters:
+            IsUpper -   True, if the matrix is upper bidiagonal.
+                        otherwise IsUpper is False.
+            D       -   the main diagonal.
+                        Array whose index ranges within [0..Min(M,N)-1].
+            E       -   the secondary diagonal (upper or lower, depending on
+                        the value of IsUpper).
+                        Array index ranges within [0..Min(M,N)-1], the last
+                        element is not used.
+
+          -- ALGLIB --
+             2005-2010
+             Bochkanov Sergey
+        *************************************************************************/
+        public static void rmatrixbdunpackdiagonals(double[,] b,
+            int m,
+            int n,
+            ref bool isupper,
+            ref double[] d,
+            ref double[] e)
+        {
+            int i = 0;
+
+            isupper = new bool();
+            d = new double[0];
+            e = new double[0];
+
+            isupper = m>=n;
+            if( m<=0 || n<=0 )
+            {
+                return;
+            }
+            if( isupper )
+            {
+                d = new double[n];
+                e = new double[n];
+                for(i=0; i<=n-2; i++)
+                {
+                    d[i] = b[i,i];
+                    e[i] = b[i,i+1];
+                }
+                d[n-1] = b[n-1,n-1];
+            }
+            else
+            {
+                d = new double[m];
+                e = new double[m];
+                for(i=0; i<=m-2; i++)
+                {
+                    d[i] = b[i,i];
+                    e[i] = b[i+1,i];
+                }
+                d[m-1] = b[m-1,m-1];
+            }
+        }
+
+
+        /*************************************************************************
+        Reduction of a square matrix to  upper Hessenberg form: Q'*A*Q = H,
+        where Q is an orthogonal matrix, H - Hessenberg matrix.
+
+        Input parameters:
+            A       -   matrix A with elements [0..N-1, 0..N-1]
+            N       -   size of matrix A.
+
+        Output parameters:
+            A       -   matrices Q and P in  compact form (see below).
+            Tau     -   array of scalar factors which are used to form matrix Q.
+                        Array whose index ranges within [0..N-2]
+
+        Matrix H is located on the main diagonal, on the lower secondary  diagonal
+        and above the main diagonal of matrix A. The elements which are used to
+        form matrix Q are situated in array Tau and below the lower secondary
+        diagonal of matrix A as follows:
+
+        Matrix Q is represented as a product of elementary reflections
+
+        Q = H(0)*H(2)*...*H(n-2),
+
+        where each H(i) is given by
+
+        H(i) = 1 - tau * v * (v^T)
+
+        where tau is a scalar stored in Tau[I]; v - is a real vector,
+        so that v(0:i) = 0, v(i+1) = 1, v(i+2:n-1) stored in A(i+2:n-1,i).
+
+          -- LAPACK routine (version 3.0) --
+             Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd.,
+             Courant Institute, Argonne National Lab, and Rice University
+             October 31, 1992
+        *************************************************************************/
+        public static void rmatrixhessenberg(ref double[,] a,
+            int n,
+            ref double[] tau)
+        {
+            int i = 0;
+            double v = 0;
+            double[] t = new double[0];
+            double[] work = new double[0];
+            int i_ = 0;
+            int i1_ = 0;
+
+            tau = new double[0];
+
+            alglib.ap.assert(n>=0, "RMatrixHessenberg: incorrect N!");
+            
+            //
+            // Quick return if possible
+            //
+            if( n<=1 )
+            {
+                return;
+            }
+            tau = new double[n-2+1];
+            t = new double[n+1];
+            work = new double[n-1+1];
+            for(i=0; i<=n-2; i++)
+            {
+                
+                //
+                // Compute elementary reflector H(i) to annihilate A(i+2:ihi,i)
+                //
+                i1_ = (i+1) - (1);
+                for(i_=1; i_<=n-i-1;i_++)
+                {
+                    t[i_] = a[i_+i1_,i];
+                }
+                reflections.generatereflection(ref t, n-i-1, ref v);
+                i1_ = (1) - (i+1);
+                for(i_=i+1; i_<=n-1;i_++)
+                {
+                    a[i_,i] = t[i_+i1_];
+                }
+                tau[i] = v;
+                t[1] = 1;
+                
+                //
+                // Apply H(i) to A(1:ihi,i+1:ihi) from the right
+                //
+                reflections.applyreflectionfromtheright(ref a, v, t, 0, n-1, i+1, n-1, ref work);
+                
+                //
+                // Apply H(i) to A(i+1:ihi,i+1:n) from the left
+                //
+                reflections.applyreflectionfromtheleft(ref a, v, t, i+1, n-1, i+1, n-1, ref work);
+            }
+        }
+
+
+        /*************************************************************************
+        Unpacking matrix Q which reduces matrix A to upper Hessenberg form
+
+        Input parameters:
+            A   -   output of RMatrixHessenberg subroutine.
+            N   -   size of matrix A.
+            Tau -   scalar factors which are used to form Q.
+                    Output of RMatrixHessenberg subroutine.
+
+        Output parameters:
+            Q   -   matrix Q.
+                    Array whose indexes range within [0..N-1, 0..N-1].
+
+          -- ALGLIB --
+             2005-2010
+             Bochkanov Sergey
+        *************************************************************************/
+        public static void rmatrixhessenbergunpackq(double[,] a,
+            int n,
+            double[] tau,
+            ref double[,] q)
+        {
+            int i = 0;
+            int j = 0;
+            double[] v = new double[0];
+            double[] work = new double[0];
+            int i_ = 0;
+            int i1_ = 0;
+
+            q = new double[0,0];
+
+            if( n==0 )
+            {
+                return;
+            }
+            
+            //
+            // init
+            //
+            q = new double[n-1+1, n-1+1];
+            v = new double[n-1+1];
+            work = new double[n-1+1];
+            for(i=0; i<=n-1; i++)
+            {
+                for(j=0; j<=n-1; j++)
+                {
+                    if( i==j )
+                    {
+                        q[i,j] = 1;
+                    }
+                    else
+                    {
+                        q[i,j] = 0;
+                    }
+                }
+            }
+            
+            //
+            // unpack Q
+            //
+            for(i=0; i<=n-2; i++)
+            {
+                
+                //
+                // Apply H(i)
+                //
+                i1_ = (i+1) - (1);
+                for(i_=1; i_<=n-i-1;i_++)
+                {
+                    v[i_] = a[i_+i1_,i];
+                }
+                v[1] = 1;
+                reflections.applyreflectionfromtheright(ref q, tau[i], v, 0, n-1, i+1, n-1, ref work);
+            }
+        }
+
+
+        /*************************************************************************
+        Unpacking matrix H (the result of matrix A reduction to upper Hessenberg form)
+
+        Input parameters:
+            A   -   output of RMatrixHessenberg subroutine.
+            N   -   size of matrix A.
+
+        Output parameters:
+            H   -   matrix H. Array whose indexes range within [0..N-1, 0..N-1].
+
+          -- ALGLIB --
+             2005-2010
+             Bochkanov Sergey
+        *************************************************************************/
+        public static void rmatrixhessenbergunpackh(double[,] a,
+            int n,
+            ref double[,] h)
+        {
+            int i = 0;
+            int j = 0;
+            double[] v = new double[0];
+            double[] work = new double[0];
+            int i_ = 0;
+
+            h = new double[0,0];
+
+            if( n==0 )
+            {
+                return;
+            }
+            h = new double[n-1+1, n-1+1];
+            for(i=0; i<=n-1; i++)
+            {
+                for(j=0; j<=i-2; j++)
+                {
+                    h[i,j] = 0;
+                }
+                j = Math.Max(0, i-1);
+                for(i_=j; i_<=n-1;i_++)
+                {
+                    h[i,i_] = a[i,i_];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Reduction of a symmetric matrix which is given by its higher or lower
+        triangular part to a tridiagonal matrix using orthogonal similarity
+        transformation: Q'*A*Q=T.
+
+        Input parameters:
+            A       -   matrix to be transformed
+                        array with elements [0..N-1, 0..N-1].
+            N       -   size of matrix A.
+            IsUpper -   storage format. If IsUpper = True, then matrix A is given
+                        by its upper triangle, and the lower triangle is not used
+                        and not modified by the algorithm, and vice versa
+                        if IsUpper = False.
+
+        Output parameters:
+            A       -   matrices T and Q in  compact form (see lower)
+            Tau     -   array of factors which are forming matrices H(i)
+                        array with elements [0..N-2].
+            D       -   main diagonal of symmetric matrix T.
+                        array with elements [0..N-1].
+            E       -   secondary diagonal of symmetric matrix T.
+                        array with elements [0..N-2].
+
+
+          If IsUpper=True, the matrix Q is represented as a product of elementary
+          reflectors
+
+             Q = H(n-2) . . . H(2) H(0).
+
+          Each H(i) has the form
+
+             H(i) = I - tau * v * v'
+
+          where tau is a real scalar, and v is a real vector with
+          v(i+1:n-1) = 0, v(i) = 1, v(0:i-1) is stored on exit in
+          A(0:i-1,i+1), and tau in TAU(i).
+
+          If IsUpper=False, the matrix Q is represented as a product of elementary
+          reflectors
+
+             Q = H(0) H(2) . . . H(n-2).
+
+          Each H(i) has the form
+
+             H(i) = I - tau * v * v'
+
+          where tau is a real scalar, and v is a real vector with
+          v(0:i) = 0, v(i+1) = 1, v(i+2:n-1) is stored on exit in A(i+2:n-1,i),
+          and tau in TAU(i).
+
+          The contents of A on exit are illustrated by the following examples
+          with n = 5:
+
+          if UPLO = 'U':                       if UPLO = 'L':
+
+            (  d   e   v1  v2  v3 )              (  d                  )
+            (      d   e   v2  v3 )              (  e   d              )
+            (          d   e   v3 )              (  v0  e   d          )
+            (              d   e  )              (  v0  v1  e   d      )
+            (                  d  )              (  v0  v1  v2  e   d  )
+
+          where d and e denote diagonal and off-diagonal elements of T, and vi
+          denotes an element of the vector defining H(i).
+
+          -- LAPACK routine (version 3.0) --
+             Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd.,
+             Courant Institute, Argonne National Lab, and Rice University
+             October 31, 1992
+        *************************************************************************/
+        public static void smatrixtd(ref double[,] a,
+            int n,
+            bool isupper,
+            ref double[] tau,
+            ref double[] d,
+            ref double[] e)
+        {
+            int i = 0;
+            double alpha = 0;
+            double taui = 0;
+            double v = 0;
+            double[] t = new double[0];
+            double[] t2 = new double[0];
+            double[] t3 = new double[0];
+            int i_ = 0;
+            int i1_ = 0;
+
+            tau = new double[0];
+            d = new double[0];
+            e = new double[0];
+
+            if( n<=0 )
+            {
+                return;
+            }
+            t = new double[n+1];
+            t2 = new double[n+1];
+            t3 = new double[n+1];
+            if( n>1 )
+            {
+                tau = new double[n-2+1];
+            }
+            d = new double[n-1+1];
+            if( n>1 )
+            {
+                e = new double[n-2+1];
+            }
+            if( isupper )
+            {
+                
+                //
+                // Reduce the upper triangle of A
+                //
+                for(i=n-2; i>=0; i--)
+                {
+                    
+                    //
+                    // Generate elementary reflector H() = E - tau * v * v'
+                    //
+                    if( i>=1 )
+                    {
+                        i1_ = (0) - (2);
+                        for(i_=2; i_<=i+1;i_++)
+                        {
+                            t[i_] = a[i_+i1_,i+1];
+                        }
+                    }
+                    t[1] = a[i,i+1];
+                    reflections.generatereflection(ref t, i+1, ref taui);
+                    if( i>=1 )
+                    {
+                        i1_ = (2) - (0);
+                        for(i_=0; i_<=i-1;i_++)
+                        {
+                            a[i_,i+1] = t[i_+i1_];
+                        }
+                    }
+                    a[i,i+1] = t[1];
+                    e[i] = a[i,i+1];
+                    if( (double)(taui)!=(double)(0) )
+                    {
+                        
+                        //
+                        // Apply H from both sides to A
+                        //
+                        a[i,i+1] = 1;
+                        
+                        //
+                        // Compute  x := tau * A * v  storing x in TAU
+                        //
+                        i1_ = (0) - (1);
+                        for(i_=1; i_<=i+1;i_++)
+                        {
+                            t[i_] = a[i_+i1_,i+1];
+                        }
+                        sblas.symmetricmatrixvectormultiply(a, isupper, 0, i, t, taui, ref t3);
+                        i1_ = (1) - (0);
+                        for(i_=0; i_<=i;i_++)
+                        {
+                            tau[i_] = t3[i_+i1_];
+                        }
+                        
+                        //
+                        // Compute  w := x - 1/2 * tau * (x'*v) * v
+                        //
+                        v = 0.0;
+                        for(i_=0; i_<=i;i_++)
+                        {
+                            v += tau[i_]*a[i_,i+1];
+                        }
+                        alpha = -(0.5*taui*v);
+                        for(i_=0; i_<=i;i_++)
+                        {
+                            tau[i_] = tau[i_] + alpha*a[i_,i+1];
+                        }
+                        
+                        //
+                        // Apply the transformation as a rank-2 update:
+                        //    A := A - v * w' - w * v'
+                        //
+                        i1_ = (0) - (1);
+                        for(i_=1; i_<=i+1;i_++)
+                        {
+                            t[i_] = a[i_+i1_,i+1];
+                        }
+                        i1_ = (0) - (1);
+                        for(i_=1; i_<=i+1;i_++)
+                        {
+                            t3[i_] = tau[i_+i1_];
+                        }
+                        sblas.symmetricrank2update(ref a, isupper, 0, i, t, t3, ref t2, -1);
+                        a[i,i+1] = e[i];
+                    }
+                    d[i+1] = a[i+1,i+1];
+                    tau[i] = taui;
+                }
+                d[0] = a[0,0];
+            }
+            else
+            {
+                
+                //
+                // Reduce the lower triangle of A
+                //
+                for(i=0; i<=n-2; i++)
+                {
+                    
+                    //
+                    // Generate elementary reflector H = E - tau * v * v'
+                    //
+                    i1_ = (i+1) - (1);
+                    for(i_=1; i_<=n-i-1;i_++)
+                    {
+                        t[i_] = a[i_+i1_,i];
+                    }
+                    reflections.generatereflection(ref t, n-i-1, ref taui);
+                    i1_ = (1) - (i+1);
+                    for(i_=i+1; i_<=n-1;i_++)
+                    {
+                        a[i_,i] = t[i_+i1_];
+                    }
+                    e[i] = a[i+1,i];
+                    if( (double)(taui)!=(double)(0) )
+                    {
+                        
+                        //
+                        // Apply H from both sides to A
+                        //
+                        a[i+1,i] = 1;
+                        
+                        //
+                        // Compute  x := tau * A * v  storing y in TAU
+                        //
+                        i1_ = (i+1) - (1);
+                        for(i_=1; i_<=n-i-1;i_++)
+                        {
+                            t[i_] = a[i_+i1_,i];
+                        }
+                        sblas.symmetricmatrixvectormultiply(a, isupper, i+1, n-1, t, taui, ref t2);
+                        i1_ = (1) - (i);
+                        for(i_=i; i_<=n-2;i_++)
+                        {
+                            tau[i_] = t2[i_+i1_];
+                        }
+                        
+                        //
+                        // Compute  w := x - 1/2 * tau * (x'*v) * v
+                        //
+                        i1_ = (i+1)-(i);
+                        v = 0.0;
+                        for(i_=i; i_<=n-2;i_++)
+                        {
+                            v += tau[i_]*a[i_+i1_,i];
+                        }
+                        alpha = -(0.5*taui*v);
+                        i1_ = (i+1) - (i);
+                        for(i_=i; i_<=n-2;i_++)
+                        {
+                            tau[i_] = tau[i_] + alpha*a[i_+i1_,i];
+                        }
+                        
+                        //
+                        // Apply the transformation as a rank-2 update:
+                        //     A := A - v * w' - w * v'
+                        //
+                        //
+                        i1_ = (i+1) - (1);
+                        for(i_=1; i_<=n-i-1;i_++)
+                        {
+                            t[i_] = a[i_+i1_,i];
+                        }
+                        i1_ = (i) - (1);
+                        for(i_=1; i_<=n-i-1;i_++)
+                        {
+                            t2[i_] = tau[i_+i1_];
+                        }
+                        sblas.symmetricrank2update(ref a, isupper, i+1, n-1, t, t2, ref t3, -1);
+                        a[i+1,i] = e[i];
+                    }
+                    d[i] = a[i,i];
+                    tau[i] = taui;
+                }
+                d[n-1] = a[n-1,n-1];
+            }
+        }
+
+
+        /*************************************************************************
+        Unpacking matrix Q which reduces symmetric matrix to a tridiagonal
+        form.
+
+        Input parameters:
+            A       -   the result of a SMatrixTD subroutine
+            N       -   size of matrix A.
+            IsUpper -   storage format (a parameter of SMatrixTD subroutine)
+            Tau     -   the result of a SMatrixTD subroutine
+
+        Output parameters:
+            Q       -   transformation matrix.
+                        array with elements [0..N-1, 0..N-1].
+
+          -- ALGLIB --
+             Copyright 2005-2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void smatrixtdunpackq(double[,] a,
+            int n,
+            bool isupper,
+            double[] tau,
+            ref double[,] q)
+        {
+            int i = 0;
+            int j = 0;
+            double[] v = new double[0];
+            double[] work = new double[0];
+            int i_ = 0;
+            int i1_ = 0;
+
+            q = new double[0,0];
+
+            if( n==0 )
+            {
+                return;
+            }
+            
+            //
+            // init
+            //
+            q = new double[n-1+1, n-1+1];
+            v = new double[n+1];
+            work = new double[n-1+1];
+            for(i=0; i<=n-1; i++)
+            {
+                for(j=0; j<=n-1; j++)
+                {
+                    if( i==j )
+                    {
+                        q[i,j] = 1;
+                    }
+                    else
+                    {
+                        q[i,j] = 0;
+                    }
+                }
+            }
+            
+            //
+            // unpack Q
+            //
+            if( isupper )
+            {
+                for(i=0; i<=n-2; i++)
+                {
+                    
+                    //
+                    // Apply H(i)
+                    //
+                    i1_ = (0) - (1);
+                    for(i_=1; i_<=i+1;i_++)
+                    {
+                        v[i_] = a[i_+i1_,i+1];
+                    }
+                    v[i+1] = 1;
+                    reflections.applyreflectionfromtheleft(ref q, tau[i], v, 0, i, 0, n-1, ref work);
+                }
+            }
+            else
+            {
+                for(i=n-2; i>=0; i--)
+                {
+                    
+                    //
+                    // Apply H(i)
+                    //
+                    i1_ = (i+1) - (1);
+                    for(i_=1; i_<=n-i-1;i_++)
+                    {
+                        v[i_] = a[i_+i1_,i];
+                    }
+                    v[1] = 1;
+                    reflections.applyreflectionfromtheleft(ref q, tau[i], v, i+1, n-1, 0, n-1, ref work);
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Reduction of a Hermitian matrix which is given  by  its  higher  or  lower
+        triangular part to a real  tridiagonal  matrix  using  unitary  similarity
+        transformation: Q'*A*Q = T.
+
+        Input parameters:
+            A       -   matrix to be transformed
+                        array with elements [0..N-1, 0..N-1].
+            N       -   size of matrix A.
+            IsUpper -   storage format. If IsUpper = True, then matrix A is  given
+                        by its upper triangle, and the lower triangle is not  used
+                        and not modified by the algorithm, and vice versa
+                        if IsUpper = False.
+
+        Output parameters:
+            A       -   matrices T and Q in  compact form (see lower)
+            Tau     -   array of factors which are forming matrices H(i)
+                        array with elements [0..N-2].
+            D       -   main diagonal of real symmetric matrix T.
+                        array with elements [0..N-1].
+            E       -   secondary diagonal of real symmetric matrix T.
+                        array with elements [0..N-2].
+
+
+          If IsUpper=True, the matrix Q is represented as a product of elementary
+          reflectors
+
+             Q = H(n-2) . . . H(2) H(0).
+
+          Each H(i) has the form
+
+             H(i) = I - tau * v * v'
+
+          where tau is a complex scalar, and v is a complex vector with
+          v(i+1:n-1) = 0, v(i) = 1, v(0:i-1) is stored on exit in
+          A(0:i-1,i+1), and tau in TAU(i).
+
+          If IsUpper=False, the matrix Q is represented as a product of elementary
+          reflectors
+
+             Q = H(0) H(2) . . . H(n-2).
+
+          Each H(i) has the form
+
+             H(i) = I - tau * v * v'
+
+          where tau is a complex scalar, and v is a complex vector with
+          v(0:i) = 0, v(i+1) = 1, v(i+2:n-1) is stored on exit in A(i+2:n-1,i),
+          and tau in TAU(i).
+
+          The contents of A on exit are illustrated by the following examples
+          with n = 5:
+
+          if UPLO = 'U':                       if UPLO = 'L':
+
+            (  d   e   v1  v2  v3 )              (  d                  )
+            (      d   e   v2  v3 )              (  e   d              )
+            (          d   e   v3 )              (  v0  e   d          )
+            (              d   e  )              (  v0  v1  e   d      )
+            (                  d  )              (  v0  v1  v2  e   d  )
+
+        where d and e denote diagonal and off-diagonal elements of T, and vi
+        denotes an element of the vector defining H(i).
+
+          -- LAPACK routine (version 3.0) --
+             Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd.,
+             Courant Institute, Argonne National Lab, and Rice University
+             October 31, 1992
+        *************************************************************************/
+        public static void hmatrixtd(ref complex[,] a,
+            int n,
+            bool isupper,
+            ref complex[] tau,
+            ref double[] d,
+            ref double[] e)
+        {
+            int i = 0;
+            complex alpha = 0;
+            complex taui = 0;
+            complex v = 0;
+            complex[] t = new complex[0];
+            complex[] t2 = new complex[0];
+            complex[] t3 = new complex[0];
+            int i_ = 0;
+            int i1_ = 0;
+
+            tau = new complex[0];
+            d = new double[0];
+            e = new double[0];
+
+            if( n<=0 )
+            {
+                return;
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                alglib.ap.assert((double)(a[i,i].y)==(double)(0));
+            }
+            if( n>1 )
+            {
+                tau = new complex[n-2+1];
+                e = new double[n-2+1];
+            }
+            d = new double[n-1+1];
+            t = new complex[n-1+1];
+            t2 = new complex[n-1+1];
+            t3 = new complex[n-1+1];
+            if( isupper )
+            {
+                
+                //
+                // Reduce the upper triangle of A
+                //
+                a[n-1,n-1] = a[n-1,n-1].x;
+                for(i=n-2; i>=0; i--)
+                {
+                    
+                    //
+                    // Generate elementary reflector H = I+1 - tau * v * v'
+                    //
+                    alpha = a[i,i+1];
+                    t[1] = alpha;
+                    if( i>=1 )
+                    {
+                        i1_ = (0) - (2);
+                        for(i_=2; i_<=i+1;i_++)
+                        {
+                            t[i_] = a[i_+i1_,i+1];
+                        }
+                    }
+                    creflections.complexgeneratereflection(ref t, i+1, ref taui);
+                    if( i>=1 )
+                    {
+                        i1_ = (2) - (0);
+                        for(i_=0; i_<=i-1;i_++)
+                        {
+                            a[i_,i+1] = t[i_+i1_];
+                        }
+                    }
+                    alpha = t[1];
+                    e[i] = alpha.x;
+                    if( taui!=0 )
+                    {
+                        
+                        //
+                        // Apply H(I+1) from both sides to A
+                        //
+                        a[i,i+1] = 1;
+                        
+                        //
+                        // Compute  x := tau * A * v  storing x in TAU
+                        //
+                        i1_ = (0) - (1);
+                        for(i_=1; i_<=i+1;i_++)
+                        {
+                            t[i_] = a[i_+i1_,i+1];
+                        }
+                        hblas.hermitianmatrixvectormultiply(a, isupper, 0, i, t, taui, ref t2);
+                        i1_ = (1) - (0);
+                        for(i_=0; i_<=i;i_++)
+                        {
+                            tau[i_] = t2[i_+i1_];
+                        }
+                        
+                        //
+                        // Compute  w := x - 1/2 * tau * (x'*v) * v
+                        //
+                        v = 0.0;
+                        for(i_=0; i_<=i;i_++)
+                        {
+                            v += math.conj(tau[i_])*a[i_,i+1];
+                        }
+                        alpha = -(0.5*taui*v);
+                        for(i_=0; i_<=i;i_++)
+                        {
+                            tau[i_] = tau[i_] + alpha*a[i_,i+1];
+                        }
+                        
+                        //
+                        // Apply the transformation as a rank-2 update:
+                        //    A := A - v * w' - w * v'
+                        //
+                        i1_ = (0) - (1);
+                        for(i_=1; i_<=i+1;i_++)
+                        {
+                            t[i_] = a[i_+i1_,i+1];
+                        }
+                        i1_ = (0) - (1);
+                        for(i_=1; i_<=i+1;i_++)
+                        {
+                            t3[i_] = tau[i_+i1_];
+                        }
+                        hblas.hermitianrank2update(ref a, isupper, 0, i, t, t3, ref t2, -1);
+                    }
+                    else
+                    {
+                        a[i,i] = a[i,i].x;
+                    }
+                    a[i,i+1] = e[i];
+                    d[i+1] = a[i+1,i+1].x;
+                    tau[i] = taui;
+                }
+                d[0] = a[0,0].x;
+            }
+            else
+            {
+                
+                //
+                // Reduce the lower triangle of A
+                //
+                a[0,0] = a[0,0].x;
+                for(i=0; i<=n-2; i++)
+                {
+                    
+                    //
+                    // Generate elementary reflector H = I - tau * v * v'
+                    //
+                    i1_ = (i+1) - (1);
+                    for(i_=1; i_<=n-i-1;i_++)
+                    {
+                        t[i_] = a[i_+i1_,i];
+                    }
+                    creflections.complexgeneratereflection(ref t, n-i-1, ref taui);
+                    i1_ = (1) - (i+1);
+                    for(i_=i+1; i_<=n-1;i_++)
+                    {
+                        a[i_,i] = t[i_+i1_];
+                    }
+                    e[i] = a[i+1,i].x;
+                    if( taui!=0 )
+                    {
+                        
+                        //
+                        // Apply H(i) from both sides to A(i+1:n,i+1:n)
+                        //
+                        a[i+1,i] = 1;
+                        
+                        //
+                        // Compute  x := tau * A * v  storing y in TAU
+                        //
+                        i1_ = (i+1) - (1);
+                        for(i_=1; i_<=n-i-1;i_++)
+                        {
+                            t[i_] = a[i_+i1_,i];
+                        }
+                        hblas.hermitianmatrixvectormultiply(a, isupper, i+1, n-1, t, taui, ref t2);
+                        i1_ = (1) - (i);
+                        for(i_=i; i_<=n-2;i_++)
+                        {
+                            tau[i_] = t2[i_+i1_];
+                        }
+                        
+                        //
+                        // Compute  w := x - 1/2 * tau * (x'*v) * v
+                        //
+                        i1_ = (i+1)-(i);
+                        v = 0.0;
+                        for(i_=i; i_<=n-2;i_++)
+                        {
+                            v += math.conj(tau[i_])*a[i_+i1_,i];
+                        }
+                        alpha = -(0.5*taui*v);
+                        i1_ = (i+1) - (i);
+                        for(i_=i; i_<=n-2;i_++)
+                        {
+                            tau[i_] = tau[i_] + alpha*a[i_+i1_,i];
+                        }
+                        
+                        //
+                        // Apply the transformation as a rank-2 update:
+                        // A := A - v * w' - w * v'
+                        //
+                        i1_ = (i+1) - (1);
+                        for(i_=1; i_<=n-i-1;i_++)
+                        {
+                            t[i_] = a[i_+i1_,i];
+                        }
+                        i1_ = (i) - (1);
+                        for(i_=1; i_<=n-i-1;i_++)
+                        {
+                            t2[i_] = tau[i_+i1_];
+                        }
+                        hblas.hermitianrank2update(ref a, isupper, i+1, n-1, t, t2, ref t3, -1);
+                    }
+                    else
+                    {
+                        a[i+1,i+1] = a[i+1,i+1].x;
+                    }
+                    a[i+1,i] = e[i];
+                    d[i] = a[i,i].x;
+                    tau[i] = taui;
+                }
+                d[n-1] = a[n-1,n-1].x;
+            }
+        }
+
+
+        /*************************************************************************
+        Unpacking matrix Q which reduces a Hermitian matrix to a real  tridiagonal
+        form.
+
+        Input parameters:
+            A       -   the result of a HMatrixTD subroutine
+            N       -   size of matrix A.
+            IsUpper -   storage format (a parameter of HMatrixTD subroutine)
+            Tau     -   the result of a HMatrixTD subroutine
+
+        Output parameters:
+            Q       -   transformation matrix.
+                        array with elements [0..N-1, 0..N-1].
+
+          -- ALGLIB --
+             Copyright 2005-2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void hmatrixtdunpackq(complex[,] a,
+            int n,
+            bool isupper,
+            complex[] tau,
+            ref complex[,] q)
+        {
+            int i = 0;
+            int j = 0;
+            complex[] v = new complex[0];
+            complex[] work = new complex[0];
+            int i_ = 0;
+            int i1_ = 0;
+
+            q = new complex[0,0];
+
+            if( n==0 )
+            {
+                return;
+            }
+            
+            //
+            // init
+            //
+            q = new complex[n-1+1, n-1+1];
+            v = new complex[n+1];
+            work = new complex[n-1+1];
+            for(i=0; i<=n-1; i++)
+            {
+                for(j=0; j<=n-1; j++)
+                {
+                    if( i==j )
+                    {
+                        q[i,j] = 1;
+                    }
+                    else
+                    {
+                        q[i,j] = 0;
+                    }
+                }
+            }
+            
+            //
+            // unpack Q
+            //
+            if( isupper )
+            {
+                for(i=0; i<=n-2; i++)
+                {
+                    
+                    //
+                    // Apply H(i)
+                    //
+                    i1_ = (0) - (1);
+                    for(i_=1; i_<=i+1;i_++)
+                    {
+                        v[i_] = a[i_+i1_,i+1];
+                    }
+                    v[i+1] = 1;
+                    creflections.complexapplyreflectionfromtheleft(ref q, tau[i], v, 0, i, 0, n-1, ref work);
+                }
+            }
+            else
+            {
+                for(i=n-2; i>=0; i--)
+                {
+                    
+                    //
+                    // Apply H(i)
+                    //
+                    i1_ = (i+1) - (1);
+                    for(i_=1; i_<=n-i-1;i_++)
+                    {
+                        v[i_] = a[i_+i1_,i];
+                    }
+                    v[1] = 1;
+                    creflections.complexapplyreflectionfromtheleft(ref q, tau[i], v, i+1, n-1, 0, n-1, ref work);
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Base case for complex QR
+
+          -- LAPACK routine (version 3.0) --
+             Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd.,
+             Courant Institute, Argonne National Lab, and Rice University
+             September 30, 1994.
+             Sergey Bochkanov, ALGLIB project, translation from FORTRAN to
+             pseudocode, 2007-2010.
+        *************************************************************************/
+        private static void cmatrixqrbasecase(ref complex[,] a,
+            int m,
+            int n,
+            ref complex[] work,
+            ref complex[] t,
+            ref complex[] tau)
+        {
+            int i = 0;
+            int k = 0;
+            int mmi = 0;
+            int minmn = 0;
+            complex tmp = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            minmn = Math.Min(m, n);
+            if( minmn<=0 )
+            {
+                return;
+            }
+            
+            //
+            // Test the input arguments
+            //
+            k = Math.Min(m, n);
+            for(i=0; i<=k-1; i++)
+            {
+                
+                //
+                // Generate elementary reflector H(i) to annihilate A(i+1:m,i)
+                //
+                mmi = m-i;
+                i1_ = (i) - (1);
+                for(i_=1; i_<=mmi;i_++)
+                {
+                    t[i_] = a[i_+i1_,i];
+                }
+                creflections.complexgeneratereflection(ref t, mmi, ref tmp);
+                tau[i] = tmp;
+                i1_ = (1) - (i);
+                for(i_=i; i_<=m-1;i_++)
+                {
+                    a[i_,i] = t[i_+i1_];
+                }
+                t[1] = 1;
+                if( i<n-1 )
+                {
+                    
+                    //
+                    // Apply H'(i) to A(i:m,i+1:n) from the left
+                    //
+                    creflections.complexapplyreflectionfromtheleft(ref a, math.conj(tau[i]), t, i, m-1, i+1, n-1, ref work);
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Base case for complex LQ
+
+          -- LAPACK routine (version 3.0) --
+             Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd.,
+             Courant Institute, Argonne National Lab, and Rice University
+             September 30, 1994.
+             Sergey Bochkanov, ALGLIB project, translation from FORTRAN to
+             pseudocode, 2007-2010.
+        *************************************************************************/
+        private static void cmatrixlqbasecase(ref complex[,] a,
+            int m,
+            int n,
+            ref complex[] work,
+            ref complex[] t,
+            ref complex[] tau)
+        {
+            int i = 0;
+            int minmn = 0;
+            complex tmp = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            minmn = Math.Min(m, n);
+            if( minmn<=0 )
+            {
+                return;
+            }
+            
+            //
+            // Test the input arguments
+            //
+            for(i=0; i<=minmn-1; i++)
+            {
+                
+                //
+                // Generate elementary reflector H(i)
+                //
+                // NOTE: ComplexGenerateReflection() generates left reflector,
+                // i.e. H which reduces x by applyiong from the left, but we
+                // need RIGHT reflector. So we replace H=E-tau*v*v' by H^H,
+                // which changes v to conj(v).
+                //
+                i1_ = (i) - (1);
+                for(i_=1; i_<=n-i;i_++)
+                {
+                    t[i_] = math.conj(a[i,i_+i1_]);
+                }
+                creflections.complexgeneratereflection(ref t, n-i, ref tmp);
+                tau[i] = tmp;
+                i1_ = (1) - (i);
+                for(i_=i; i_<=n-1;i_++)
+                {
+                    a[i,i_] = math.conj(t[i_+i1_]);
+                }
+                t[1] = 1;
+                if( i<m-1 )
+                {
+                    
+                    //
+                    // Apply H'(i)
+                    //
+                    creflections.complexapplyreflectionfromtheright(ref a, tau[i], ref t, i+1, m-1, i, n-1, ref work);
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Generate block reflector:
+        * fill unused parts of reflectors matrix by zeros
+        * fill diagonal of reflectors matrix by ones
+        * generate triangular factor T
+
+        PARAMETERS:
+            A           -   either LengthA*BlockSize (if ColumnwiseA) or
+                            BlockSize*LengthA (if not ColumnwiseA) matrix of
+                            elementary reflectors.
+                            Modified on exit.
+            Tau         -   scalar factors
+            ColumnwiseA -   reflectors are stored in rows or in columns
+            LengthA     -   length of largest reflector
+            BlockSize   -   number of reflectors
+            T           -   array[BlockSize,2*BlockSize]. Left BlockSize*BlockSize
+                            submatrix stores triangular factor on exit.
+            WORK        -   array[BlockSize]
+            
+          -- ALGLIB routine --
+             17.02.2010
+             Bochkanov Sergey
+        *************************************************************************/
+        private static void rmatrixblockreflector(ref double[,] a,
+            ref double[] tau,
+            bool columnwisea,
+            int lengtha,
+            int blocksize,
+            ref double[,] t,
+            ref double[] work)
+        {
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            double v = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            
+            //
+            // fill beginning of new column with zeros,
+            // load 1.0 in the first non-zero element
+            //
+            for(k=0; k<=blocksize-1; k++)
+            {
+                if( columnwisea )
+                {
+                    for(i=0; i<=k-1; i++)
+                    {
+                        a[i,k] = 0;
+                    }
+                }
+                else
+                {
+                    for(i=0; i<=k-1; i++)
+                    {
+                        a[k,i] = 0;
+                    }
+                }
+                a[k,k] = 1;
+            }
+            
+            //
+            // Calculate Gram matrix of A
+            //
+            for(i=0; i<=blocksize-1; i++)
+            {
+                for(j=0; j<=blocksize-1; j++)
+                {
+                    t[i,blocksize+j] = 0;
+                }
+            }
+            for(k=0; k<=lengtha-1; k++)
+            {
+                for(j=1; j<=blocksize-1; j++)
+                {
+                    if( columnwisea )
+                    {
+                        v = a[k,j];
+                        if( (double)(v)!=(double)(0) )
+                        {
+                            i1_ = (0) - (blocksize);
+                            for(i_=blocksize; i_<=blocksize+j-1;i_++)
+                            {
+                                t[j,i_] = t[j,i_] + v*a[k,i_+i1_];
+                            }
+                        }
+                    }
+                    else
+                    {
+                        v = a[j,k];
+                        if( (double)(v)!=(double)(0) )
+                        {
+                            i1_ = (0) - (blocksize);
+                            for(i_=blocksize; i_<=blocksize+j-1;i_++)
+                            {
+                                t[j,i_] = t[j,i_] + v*a[i_+i1_,k];
+                            }
+                        }
+                    }
+                }
+            }
+            
+            //
+            // Prepare Y (stored in TmpA) and T (stored in TmpT)
+            //
+            for(k=0; k<=blocksize-1; k++)
+            {
+                
+                //
+                // fill non-zero part of T, use pre-calculated Gram matrix
+                //
+                i1_ = (blocksize) - (0);
+                for(i_=0; i_<=k-1;i_++)
+                {
+                    work[i_] = t[k,i_+i1_];
+                }
+                for(i=0; i<=k-1; i++)
+                {
+                    v = 0.0;
+                    for(i_=i; i_<=k-1;i_++)
+                    {
+                        v += t[i,i_]*work[i_];
+                    }
+                    t[i,k] = -(tau[k]*v);
+                }
+                t[k,k] = -tau[k];
+                
+                //
+                // Rest of T is filled by zeros
+                //
+                for(i=k+1; i<=blocksize-1; i++)
+                {
+                    t[i,k] = 0;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Generate block reflector (complex):
+        * fill unused parts of reflectors matrix by zeros
+        * fill diagonal of reflectors matrix by ones
+        * generate triangular factor T
+
+
+          -- ALGLIB routine --
+             17.02.2010
+             Bochkanov Sergey
+        *************************************************************************/
+        private static void cmatrixblockreflector(ref complex[,] a,
+            ref complex[] tau,
+            bool columnwisea,
+            int lengtha,
+            int blocksize,
+            ref complex[,] t,
+            ref complex[] work)
+        {
+            int i = 0;
+            int k = 0;
+            complex v = 0;
+            int i_ = 0;
+
+            
+            //
+            // Prepare Y (stored in TmpA) and T (stored in TmpT)
+            //
+            for(k=0; k<=blocksize-1; k++)
+            {
+                
+                //
+                // fill beginning of new column with zeros,
+                // load 1.0 in the first non-zero element
+                //
+                if( columnwisea )
+                {
+                    for(i=0; i<=k-1; i++)
+                    {
+                        a[i,k] = 0;
+                    }
+                }
+                else
+                {
+                    for(i=0; i<=k-1; i++)
+                    {
+                        a[k,i] = 0;
+                    }
+                }
+                a[k,k] = 1;
+                
+                //
+                // fill non-zero part of T,
+                //
+                for(i=0; i<=k-1; i++)
+                {
+                    if( columnwisea )
+                    {
+                        v = 0.0;
+                        for(i_=k; i_<=lengtha-1;i_++)
+                        {
+                            v += math.conj(a[i_,i])*a[i_,k];
+                        }
+                    }
+                    else
+                    {
+                        v = 0.0;
+                        for(i_=k; i_<=lengtha-1;i_++)
+                        {
+                            v += a[i,i_]*math.conj(a[k,i_]);
+                        }
+                    }
+                    work[i] = v;
+                }
+                for(i=0; i<=k-1; i++)
+                {
+                    v = 0.0;
+                    for(i_=i; i_<=k-1;i_++)
+                    {
+                        v += t[i,i_]*work[i_];
+                    }
+                    t[i,k] = -(tau[k]*v);
+                }
+                t[k,k] = -tau[k];
+                
+                //
+                // Rest of T is filled by zeros
+                //
+                for(i=k+1; i<=blocksize-1; i++)
+                {
+                    t[i,k] = 0;
+                }
+            }
+        }
+
+
+    }
+    public class bdsvd
+    {
+        /*************************************************************************
+        Singular value decomposition of a bidiagonal matrix (extended algorithm)
+
+        The algorithm performs the singular value decomposition  of  a  bidiagonal
+        matrix B (upper or lower) representing it as B = Q*S*P^T, where Q and  P -
+        orthogonal matrices, S - diagonal matrix with non-negative elements on the
+        main diagonal, in descending order.
+
+        The  algorithm  finds  singular  values.  In  addition,  the algorithm can
+        calculate  matrices  Q  and P (more precisely, not the matrices, but their
+        product  with  given  matrices U and VT - U*Q and (P^T)*VT)).  Of  course,
+        matrices U and VT can be of any type, including identity. Furthermore, the
+        algorithm can calculate Q'*C (this product is calculated more  effectively
+        than U*Q,  because  this calculation operates with rows instead  of matrix
+        columns).
+
+        The feature of the algorithm is its ability to find  all  singular  values
+        including those which are arbitrarily close to 0  with  relative  accuracy
+        close to  machine precision. If the parameter IsFractionalAccuracyRequired
+        is set to True, all singular values will have high relative accuracy close
+        to machine precision. If the parameter is set to False, only  the  biggest
+        singular value will have relative accuracy  close  to  machine  precision.
+        The absolute error of other singular values is equal to the absolute error
+        of the biggest singular value.
+
+        Input parameters:
+            D       -   main diagonal of matrix B.
+                        Array whose index ranges within [0..N-1].
+            E       -   superdiagonal (or subdiagonal) of matrix B.
+                        Array whose index ranges within [0..N-2].
+            N       -   size of matrix B.
+            IsUpper -   True, if the matrix is upper bidiagonal.
+            IsFractionalAccuracyRequired -
+                        THIS PARAMETER IS IGNORED SINCE ALGLIB 3.5.0
+                        SINGULAR VALUES ARE ALWAYS SEARCHED WITH HIGH ACCURACY.
+            U       -   matrix to be multiplied by Q.
+                        Array whose indexes range within [0..NRU-1, 0..N-1].
+                        The matrix can be bigger, in that case only the  submatrix
+                        [0..NRU-1, 0..N-1] will be multiplied by Q.
+            NRU     -   number of rows in matrix U.
+            C       -   matrix to be multiplied by Q'.
+                        Array whose indexes range within [0..N-1, 0..NCC-1].
+                        The matrix can be bigger, in that case only the  submatrix
+                        [0..N-1, 0..NCC-1] will be multiplied by Q'.
+            NCC     -   number of columns in matrix C.
+            VT      -   matrix to be multiplied by P^T.
+                        Array whose indexes range within [0..N-1, 0..NCVT-1].
+                        The matrix can be bigger, in that case only the  submatrix
+                        [0..N-1, 0..NCVT-1] will be multiplied by P^T.
+            NCVT    -   number of columns in matrix VT.
+
+        Output parameters:
+            D       -   singular values of matrix B in descending order.
+            U       -   if NRU>0, contains matrix U*Q.
+            VT      -   if NCVT>0, contains matrix (P^T)*VT.
+            C       -   if NCC>0, contains matrix Q'*C.
+
+        Result:
+            True, if the algorithm has converged.
+            False, if the algorithm hasn't converged (rare case).
+
+        Additional information:
+            The type of convergence is controlled by the internal  parameter  TOL.
+            If the parameter is greater than 0, the singular values will have
+            relative accuracy TOL. If TOL<0, the singular values will have
+            absolute accuracy ABS(TOL)*norm(B).
+            By default, |TOL| falls within the range of 10*Epsilon and 100*Epsilon,
+            where Epsilon is the machine precision. It is not  recommended  to  use
+            TOL less than 10*Epsilon since this will  considerably  slow  down  the
+            algorithm and may not lead to error decreasing.
+        History:
+            * 31 March, 2007.
+                changed MAXITR from 6 to 12.
+
+          -- LAPACK routine (version 3.0) --
+             Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd.,
+             Courant Institute, Argonne National Lab, and Rice University
+             October 31, 1999.
+        *************************************************************************/
+        public static bool rmatrixbdsvd(ref double[] d,
+            double[] e,
+            int n,
+            bool isupper,
+            bool isfractionalaccuracyrequired,
+            ref double[,] u,
+            int nru,
+            ref double[,] c,
+            int ncc,
+            ref double[,] vt,
+            int ncvt)
+        {
+            bool result = new bool();
+            double[] d1 = new double[0];
+            double[] e1 = new double[0];
+            int i_ = 0;
+            int i1_ = 0;
+
+            e = (double[])e.Clone();
+
+            d1 = new double[n+1];
+            i1_ = (0) - (1);
+            for(i_=1; i_<=n;i_++)
+            {
+                d1[i_] = d[i_+i1_];
+            }
+            if( n>1 )
+            {
+                e1 = new double[n-1+1];
+                i1_ = (0) - (1);
+                for(i_=1; i_<=n-1;i_++)
+                {
+                    e1[i_] = e[i_+i1_];
+                }
+            }
+            result = bidiagonalsvddecompositioninternal(ref d1, e1, n, isupper, isfractionalaccuracyrequired, ref u, 0, nru, ref c, 0, ncc, ref vt, 0, ncvt);
+            i1_ = (1) - (0);
+            for(i_=0; i_<=n-1;i_++)
+            {
+                d[i_] = d1[i_+i1_];
+            }
+            return result;
+        }
+
+
+        public static bool bidiagonalsvddecomposition(ref double[] d,
+            double[] e,
+            int n,
+            bool isupper,
+            bool isfractionalaccuracyrequired,
+            ref double[,] u,
+            int nru,
+            ref double[,] c,
+            int ncc,
+            ref double[,] vt,
+            int ncvt)
+        {
+            bool result = new bool();
+
+            e = (double[])e.Clone();
+
+            result = bidiagonalsvddecompositioninternal(ref d, e, n, isupper, isfractionalaccuracyrequired, ref u, 1, nru, ref c, 1, ncc, ref vt, 1, ncvt);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Internal working subroutine for bidiagonal decomposition
+        *************************************************************************/
+        private static bool bidiagonalsvddecompositioninternal(ref double[] d,
+            double[] e,
+            int n,
+            bool isupper,
+            bool isfractionalaccuracyrequired,
+            ref double[,] u,
+            int ustart,
+            int nru,
+            ref double[,] c,
+            int cstart,
+            int ncc,
+            ref double[,] vt,
+            int vstart,
+            int ncvt)
+        {
+            bool result = new bool();
+            int i = 0;
+            int idir = 0;
+            int isub = 0;
+            int iter = 0;
+            int j = 0;
+            int ll = 0;
+            int lll = 0;
+            int m = 0;
+            int maxit = 0;
+            int oldll = 0;
+            int oldm = 0;
+            double abse = 0;
+            double abss = 0;
+            double cosl = 0;
+            double cosr = 0;
+            double cs = 0;
+            double eps = 0;
+            double f = 0;
+            double g = 0;
+            double h = 0;
+            double mu = 0;
+            double oldcs = 0;
+            double oldsn = 0;
+            double r = 0;
+            double shift = 0;
+            double sigmn = 0;
+            double sigmx = 0;
+            double sinl = 0;
+            double sinr = 0;
+            double sll = 0;
+            double smax = 0;
+            double smin = 0;
+            double sminl = 0;
+            double sminlo = 0;
+            double sminoa = 0;
+            double sn = 0;
+            double thresh = 0;
+            double tol = 0;
+            double tolmul = 0;
+            double unfl = 0;
+            double[] work0 = new double[0];
+            double[] work1 = new double[0];
+            double[] work2 = new double[0];
+            double[] work3 = new double[0];
+            int maxitr = 0;
+            bool matrixsplitflag = new bool();
+            bool iterflag = new bool();
+            double[] utemp = new double[0];
+            double[] vttemp = new double[0];
+            double[] ctemp = new double[0];
+            double[] etemp = new double[0];
+            bool rightside = new bool();
+            bool fwddir = new bool();
+            double tmp = 0;
+            int mm1 = 0;
+            int mm0 = 0;
+            bool bchangedir = new bool();
+            int uend = 0;
+            int cend = 0;
+            int vend = 0;
+            int i_ = 0;
+
+            e = (double[])e.Clone();
+
+            result = true;
+            if( n==0 )
+            {
+                return result;
+            }
+            if( n==1 )
+            {
+                if( (double)(d[1])<(double)(0) )
+                {
+                    d[1] = -d[1];
+                    if( ncvt>0 )
+                    {
+                        for(i_=vstart; i_<=vstart+ncvt-1;i_++)
+                        {
+                            vt[vstart,i_] = -1*vt[vstart,i_];
+                        }
+                    }
+                }
+                return result;
+            }
+            
+            //
+            // these initializers are not really necessary,
+            // but without them compiler complains about uninitialized locals
+            //
+            ll = 0;
+            oldsn = 0;
+            
+            //
+            // init
+            //
+            work0 = new double[n-1+1];
+            work1 = new double[n-1+1];
+            work2 = new double[n-1+1];
+            work3 = new double[n-1+1];
+            uend = ustart+Math.Max(nru-1, 0);
+            vend = vstart+Math.Max(ncvt-1, 0);
+            cend = cstart+Math.Max(ncc-1, 0);
+            utemp = new double[uend+1];
+            vttemp = new double[vend+1];
+            ctemp = new double[cend+1];
+            maxitr = 12;
+            rightside = true;
+            fwddir = true;
+            
+            //
+            // resize E from N-1 to N
+            //
+            etemp = new double[n+1];
+            for(i=1; i<=n-1; i++)
+            {
+                etemp[i] = e[i];
+            }
+            e = new double[n+1];
+            for(i=1; i<=n-1; i++)
+            {
+                e[i] = etemp[i];
+            }
+            e[n] = 0;
+            idir = 0;
+            
+            //
+            // Get machine constants
+            //
+            eps = math.machineepsilon;
+            unfl = math.minrealnumber;
+            
+            //
+            // If matrix lower bidiagonal, rotate to be upper bidiagonal
+            // by applying Givens rotations on the left
+            //
+            if( !isupper )
+            {
+                for(i=1; i<=n-1; i++)
+                {
+                    rotations.generaterotation(d[i], e[i], ref cs, ref sn, ref r);
+                    d[i] = r;
+                    e[i] = sn*d[i+1];
+                    d[i+1] = cs*d[i+1];
+                    work0[i] = cs;
+                    work1[i] = sn;
+                }
+                
+                //
+                // Update singular vectors if desired
+                //
+                if( nru>0 )
+                {
+                    rotations.applyrotationsfromtheright(fwddir, ustart, uend, 1+ustart-1, n+ustart-1, work0, work1, ref u, ref utemp);
+                }
+                if( ncc>0 )
+                {
+                    rotations.applyrotationsfromtheleft(fwddir, 1+cstart-1, n+cstart-1, cstart, cend, work0, work1, ref c, ref ctemp);
+                }
+            }
+            
+            //
+            // Compute singular values to relative accuracy TOL
+            // (By setting TOL to be negative, algorithm will compute
+            // singular values to absolute accuracy ABS(TOL)*norm(input matrix))
+            //
+            tolmul = Math.Max(10, Math.Min(100, Math.Pow(eps, -0.125)));
+            tol = tolmul*eps;
+            
+            //
+            // Compute approximate maximum, minimum singular values
+            //
+            smax = 0;
+            for(i=1; i<=n; i++)
+            {
+                smax = Math.Max(smax, Math.Abs(d[i]));
+            }
+            for(i=1; i<=n-1; i++)
+            {
+                smax = Math.Max(smax, Math.Abs(e[i]));
+            }
+            sminl = 0;
+            if( (double)(tol)>=(double)(0) )
+            {
+                
+                //
+                // Relative accuracy desired
+                //
+                sminoa = Math.Abs(d[1]);
+                if( (double)(sminoa)!=(double)(0) )
+                {
+                    mu = sminoa;
+                    for(i=2; i<=n; i++)
+                    {
+                        mu = Math.Abs(d[i])*(mu/(mu+Math.Abs(e[i-1])));
+                        sminoa = Math.Min(sminoa, mu);
+                        if( (double)(sminoa)==(double)(0) )
+                        {
+                            break;
+                        }
+                    }
+                }
+                sminoa = sminoa/Math.Sqrt(n);
+                thresh = Math.Max(tol*sminoa, maxitr*n*n*unfl);
+            }
+            else
+            {
+                
+                //
+                // Absolute accuracy desired
+                //
+                thresh = Math.Max(Math.Abs(tol)*smax, maxitr*n*n*unfl);
+            }
+            
+            //
+            // Prepare for main iteration loop for the singular values
+            // (MAXIT is the maximum number of passes through the inner
+            // loop permitted before nonconvergence signalled.)
+            //
+            maxit = maxitr*n*n;
+            iter = 0;
+            oldll = -1;
+            oldm = -1;
+            
+            //
+            // M points to last element of unconverged part of matrix
+            //
+            m = n;
+            
+            //
+            // Begin main iteration loop
+            //
+            while( true )
+            {
+                
+                //
+                // Check for convergence or exceeding iteration count
+                //
+                if( m<=1 )
+                {
+                    break;
+                }
+                if( iter>maxit )
+                {
+                    result = false;
+                    return result;
+                }
+                
+                //
+                // Find diagonal block of matrix to work on
+                //
+                if( (double)(tol)<(double)(0) && (double)(Math.Abs(d[m]))<=(double)(thresh) )
+                {
+                    d[m] = 0;
+                }
+                smax = Math.Abs(d[m]);
+                smin = smax;
+                matrixsplitflag = false;
+                for(lll=1; lll<=m-1; lll++)
+                {
+                    ll = m-lll;
+                    abss = Math.Abs(d[ll]);
+                    abse = Math.Abs(e[ll]);
+                    if( (double)(tol)<(double)(0) && (double)(abss)<=(double)(thresh) )
+                    {
+                        d[ll] = 0;
+                    }
+                    if( (double)(abse)<=(double)(thresh) )
+                    {
+                        matrixsplitflag = true;
+                        break;
+                    }
+                    smin = Math.Min(smin, abss);
+                    smax = Math.Max(smax, Math.Max(abss, abse));
+                }
+                if( !matrixsplitflag )
+                {
+                    ll = 0;
+                }
+                else
+                {
+                    
+                    //
+                    // Matrix splits since E(LL) = 0
+                    //
+                    e[ll] = 0;
+                    if( ll==m-1 )
+                    {
+                        
+                        //
+                        // Convergence of bottom singular value, return to top of loop
+                        //
+                        m = m-1;
+                        continue;
+                    }
+                }
+                ll = ll+1;
+                
+                //
+                // E(LL) through E(M-1) are nonzero, E(LL-1) is zero
+                //
+                if( ll==m-1 )
+                {
+                    
+                    //
+                    // 2 by 2 block, handle separately
+                    //
+                    svdv2x2(d[m-1], e[m-1], d[m], ref sigmn, ref sigmx, ref sinr, ref cosr, ref sinl, ref cosl);
+                    d[m-1] = sigmx;
+                    e[m-1] = 0;
+                    d[m] = sigmn;
+                    
+                    //
+                    // Compute singular vectors, if desired
+                    //
+                    if( ncvt>0 )
+                    {
+                        mm0 = m+(vstart-1);
+                        mm1 = m-1+(vstart-1);
+                        for(i_=vstart; i_<=vend;i_++)
+                        {
+                            vttemp[i_] = cosr*vt[mm1,i_];
+                        }
+                        for(i_=vstart; i_<=vend;i_++)
+                        {
+                            vttemp[i_] = vttemp[i_] + sinr*vt[mm0,i_];
+                        }
+                        for(i_=vstart; i_<=vend;i_++)
+                        {
+                            vt[mm0,i_] = cosr*vt[mm0,i_];
+                        }
+                        for(i_=vstart; i_<=vend;i_++)
+                        {
+                            vt[mm0,i_] = vt[mm0,i_] - sinr*vt[mm1,i_];
+                        }
+                        for(i_=vstart; i_<=vend;i_++)
+                        {
+                            vt[mm1,i_] = vttemp[i_];
+                        }
+                    }
+                    if( nru>0 )
+                    {
+                        mm0 = m+ustart-1;
+                        mm1 = m-1+ustart-1;
+                        for(i_=ustart; i_<=uend;i_++)
+                        {
+                            utemp[i_] = cosl*u[i_,mm1];
+                        }
+                        for(i_=ustart; i_<=uend;i_++)
+                        {
+                            utemp[i_] = utemp[i_] + sinl*u[i_,mm0];
+                        }
+                        for(i_=ustart; i_<=uend;i_++)
+                        {
+                            u[i_,mm0] = cosl*u[i_,mm0];
+                        }
+                        for(i_=ustart; i_<=uend;i_++)
+                        {
+                            u[i_,mm0] = u[i_,mm0] - sinl*u[i_,mm1];
+                        }
+                        for(i_=ustart; i_<=uend;i_++)
+                        {
+                            u[i_,mm1] = utemp[i_];
+                        }
+                    }
+                    if( ncc>0 )
+                    {
+                        mm0 = m+cstart-1;
+                        mm1 = m-1+cstart-1;
+                        for(i_=cstart; i_<=cend;i_++)
+                        {
+                            ctemp[i_] = cosl*c[mm1,i_];
+                        }
+                        for(i_=cstart; i_<=cend;i_++)
+                        {
+                            ctemp[i_] = ctemp[i_] + sinl*c[mm0,i_];
+                        }
+                        for(i_=cstart; i_<=cend;i_++)
+                        {
+                            c[mm0,i_] = cosl*c[mm0,i_];
+                        }
+                        for(i_=cstart; i_<=cend;i_++)
+                        {
+                            c[mm0,i_] = c[mm0,i_] - sinl*c[mm1,i_];
+                        }
+                        for(i_=cstart; i_<=cend;i_++)
+                        {
+                            c[mm1,i_] = ctemp[i_];
+                        }
+                    }
+                    m = m-2;
+                    continue;
+                }
+                
+                //
+                // If working on new submatrix, choose shift direction
+                // (from larger end diagonal element towards smaller)
+                //
+                // Previously was
+                //     "if (LL>OLDM) or (M<OLDLL) then"
+                // fixed thanks to Michael Rolle < m@rolle.name >
+                // Very strange that LAPACK still contains it.
+                //
+                bchangedir = false;
+                if( idir==1 && (double)(Math.Abs(d[ll]))<(double)(1.0E-3*Math.Abs(d[m])) )
+                {
+                    bchangedir = true;
+                }
+                if( idir==2 && (double)(Math.Abs(d[m]))<(double)(1.0E-3*Math.Abs(d[ll])) )
+                {
+                    bchangedir = true;
+                }
+                if( (ll!=oldll || m!=oldm) || bchangedir )
+                {
+                    if( (double)(Math.Abs(d[ll]))>=(double)(Math.Abs(d[m])) )
+                    {
+                        
+                        //
+                        // Chase bulge from top (big end) to bottom (small end)
+                        //
+                        idir = 1;
+                    }
+                    else
+                    {
+                        
+                        //
+                        // Chase bulge from bottom (big end) to top (small end)
+                        //
+                        idir = 2;
+                    }
+                }
+                
+                //
+                // Apply convergence tests
+                //
+                if( idir==1 )
+                {
+                    
+                    //
+                    // Run convergence test in forward direction
+                    // First apply standard test to bottom of matrix
+                    //
+                    if( (double)(Math.Abs(e[m-1]))<=(double)(Math.Abs(tol)*Math.Abs(d[m])) || ((double)(tol)<(double)(0) && (double)(Math.Abs(e[m-1]))<=(double)(thresh)) )
+                    {
+                        e[m-1] = 0;
+                        continue;
+                    }
+                    if( (double)(tol)>=(double)(0) )
+                    {
+                        
+                        //
+                        // If relative accuracy desired,
+                        // apply convergence criterion forward
+                        //
+                        mu = Math.Abs(d[ll]);
+                        sminl = mu;
+                        iterflag = false;
+                        for(lll=ll; lll<=m-1; lll++)
+                        {
+                            if( (double)(Math.Abs(e[lll]))<=(double)(tol*mu) )
+                            {
+                                e[lll] = 0;
+                                iterflag = true;
+                                break;
+                            }
+                            sminlo = sminl;
+                            mu = Math.Abs(d[lll+1])*(mu/(mu+Math.Abs(e[lll])));
+                            sminl = Math.Min(sminl, mu);
+                        }
+                        if( iterflag )
+                        {
+                            continue;
+                        }
+                    }
+                }
+                else
+                {
+                    
+                    //
+                    // Run convergence test in backward direction
+                    // First apply standard test to top of matrix
+                    //
+                    if( (double)(Math.Abs(e[ll]))<=(double)(Math.Abs(tol)*Math.Abs(d[ll])) || ((double)(tol)<(double)(0) && (double)(Math.Abs(e[ll]))<=(double)(thresh)) )
+                    {
+                        e[ll] = 0;
+                        continue;
+                    }
+                    if( (double)(tol)>=(double)(0) )
+                    {
+                        
+                        //
+                        // If relative accuracy desired,
+                        // apply convergence criterion backward
+                        //
+                        mu = Math.Abs(d[m]);
+                        sminl = mu;
+                        iterflag = false;
+                        for(lll=m-1; lll>=ll; lll--)
+                        {
+                            if( (double)(Math.Abs(e[lll]))<=(double)(tol*mu) )
+                            {
+                                e[lll] = 0;
+                                iterflag = true;
+                                break;
+                            }
+                            sminlo = sminl;
+                            mu = Math.Abs(d[lll])*(mu/(mu+Math.Abs(e[lll])));
+                            sminl = Math.Min(sminl, mu);
+                        }
+                        if( iterflag )
+                        {
+                            continue;
+                        }
+                    }
+                }
+                oldll = ll;
+                oldm = m;
+                
+                //
+                // Compute shift.  First, test if shifting would ruin relative
+                // accuracy, and if so set the shift to zero.
+                //
+                if( (double)(tol)>=(double)(0) && (double)(n*tol*(sminl/smax))<=(double)(Math.Max(eps, 0.01*tol)) )
+                {
+                    
+                    //
+                    // Use a zero shift to avoid loss of relative accuracy
+                    //
+                    shift = 0;
+                }
+                else
+                {
+                    
+                    //
+                    // Compute the shift from 2-by-2 block at end of matrix
+                    //
+                    if( idir==1 )
+                    {
+                        sll = Math.Abs(d[ll]);
+                        svd2x2(d[m-1], e[m-1], d[m], ref shift, ref r);
+                    }
+                    else
+                    {
+                        sll = Math.Abs(d[m]);
+                        svd2x2(d[ll], e[ll], d[ll+1], ref shift, ref r);
+                    }
+                    
+                    //
+                    // Test if shift negligible, and if so set to zero
+                    //
+                    if( (double)(sll)>(double)(0) )
+                    {
+                        if( (double)(math.sqr(shift/sll))<(double)(eps) )
+                        {
+                            shift = 0;
+                        }
+                    }
+                }
+                
+                //
+                // Increment iteration count
+                //
+                iter = iter+m-ll;
+                
+                //
+                // If SHIFT = 0, do simplified QR iteration
+                //
+                if( (double)(shift)==(double)(0) )
+                {
+                    if( idir==1 )
+                    {
+                        
+                        //
+                        // Chase bulge from top to bottom
+                        // Save cosines and sines for later singular vector updates
+                        //
+                        cs = 1;
+                        oldcs = 1;
+                        for(i=ll; i<=m-1; i++)
+                        {
+                            rotations.generaterotation(d[i]*cs, e[i], ref cs, ref sn, ref r);
+                            if( i>ll )
+                            {
+                                e[i-1] = oldsn*r;
+                            }
+                            rotations.generaterotation(oldcs*r, d[i+1]*sn, ref oldcs, ref oldsn, ref tmp);
+                            d[i] = tmp;
+                            work0[i-ll+1] = cs;
+                            work1[i-ll+1] = sn;
+                            work2[i-ll+1] = oldcs;
+                            work3[i-ll+1] = oldsn;
+                        }
+                        h = d[m]*cs;
+                        d[m] = h*oldcs;
+                        e[m-1] = h*oldsn;
+                        
+                        //
+                        // Update singular vectors
+                        //
+                        if( ncvt>0 )
+                        {
+                            rotations.applyrotationsfromtheleft(fwddir, ll+vstart-1, m+vstart-1, vstart, vend, work0, work1, ref vt, ref vttemp);
+                        }
+                        if( nru>0 )
+                        {
+                            rotations.applyrotationsfromtheright(fwddir, ustart, uend, ll+ustart-1, m+ustart-1, work2, work3, ref u, ref utemp);
+                        }
+                        if( ncc>0 )
+                        {
+                            rotations.applyrotationsfromtheleft(fwddir, ll+cstart-1, m+cstart-1, cstart, cend, work2, work3, ref c, ref ctemp);
+                        }
+                        
+                        //
+                        // Test convergence
+                        //
+                        if( (double)(Math.Abs(e[m-1]))<=(double)(thresh) )
+                        {
+                            e[m-1] = 0;
+                        }
+                    }
+                    else
+                    {
+                        
+                        //
+                        // Chase bulge from bottom to top
+                        // Save cosines and sines for later singular vector updates
+                        //
+                        cs = 1;
+                        oldcs = 1;
+                        for(i=m; i>=ll+1; i--)
+                        {
+                            rotations.generaterotation(d[i]*cs, e[i-1], ref cs, ref sn, ref r);
+                            if( i<m )
+                            {
+                                e[i] = oldsn*r;
+                            }
+                            rotations.generaterotation(oldcs*r, d[i-1]*sn, ref oldcs, ref oldsn, ref tmp);
+                            d[i] = tmp;
+                            work0[i-ll] = cs;
+                            work1[i-ll] = -sn;
+                            work2[i-ll] = oldcs;
+                            work3[i-ll] = -oldsn;
+                        }
+                        h = d[ll]*cs;
+                        d[ll] = h*oldcs;
+                        e[ll] = h*oldsn;
+                        
+                        //
+                        // Update singular vectors
+                        //
+                        if( ncvt>0 )
+                        {
+                            rotations.applyrotationsfromtheleft(!fwddir, ll+vstart-1, m+vstart-1, vstart, vend, work2, work3, ref vt, ref vttemp);
+                        }
+                        if( nru>0 )
+                        {
+                            rotations.applyrotationsfromtheright(!fwddir, ustart, uend, ll+ustart-1, m+ustart-1, work0, work1, ref u, ref utemp);
+                        }
+                        if( ncc>0 )
+                        {
+                            rotations.applyrotationsfromtheleft(!fwddir, ll+cstart-1, m+cstart-1, cstart, cend, work0, work1, ref c, ref ctemp);
+                        }
+                        
+                        //
+                        // Test convergence
+                        //
+                        if( (double)(Math.Abs(e[ll]))<=(double)(thresh) )
+                        {
+                            e[ll] = 0;
+                        }
+                    }
+                }
+                else
+                {
+                    
+                    //
+                    // Use nonzero shift
+                    //
+                    if( idir==1 )
+                    {
+                        
+                        //
+                        // Chase bulge from top to bottom
+                        // Save cosines and sines for later singular vector updates
+                        //
+                        f = (Math.Abs(d[ll])-shift)*(extsignbdsqr(1, d[ll])+shift/d[ll]);
+                        g = e[ll];
+                        for(i=ll; i<=m-1; i++)
+                        {
+                            rotations.generaterotation(f, g, ref cosr, ref sinr, ref r);
+                            if( i>ll )
+                            {
+                                e[i-1] = r;
+                            }
+                            f = cosr*d[i]+sinr*e[i];
+                            e[i] = cosr*e[i]-sinr*d[i];
+                            g = sinr*d[i+1];
+                            d[i+1] = cosr*d[i+1];
+                            rotations.generaterotation(f, g, ref cosl, ref sinl, ref r);
+                            d[i] = r;
+                            f = cosl*e[i]+sinl*d[i+1];
+                            d[i+1] = cosl*d[i+1]-sinl*e[i];
+                            if( i<m-1 )
+                            {
+                                g = sinl*e[i+1];
+                                e[i+1] = cosl*e[i+1];
+                            }
+                            work0[i-ll+1] = cosr;
+                            work1[i-ll+1] = sinr;
+                            work2[i-ll+1] = cosl;
+                            work3[i-ll+1] = sinl;
+                        }
+                        e[m-1] = f;
+                        
+                        //
+                        // Update singular vectors
+                        //
+                        if( ncvt>0 )
+                        {
+                            rotations.applyrotationsfromtheleft(fwddir, ll+vstart-1, m+vstart-1, vstart, vend, work0, work1, ref vt, ref vttemp);
+                        }
+                        if( nru>0 )
+                        {
+                            rotations.applyrotationsfromtheright(fwddir, ustart, uend, ll+ustart-1, m+ustart-1, work2, work3, ref u, ref utemp);
+                        }
+                        if( ncc>0 )
+                        {
+                            rotations.applyrotationsfromtheleft(fwddir, ll+cstart-1, m+cstart-1, cstart, cend, work2, work3, ref c, ref ctemp);
+                        }
+                        
+                        //
+                        // Test convergence
+                        //
+                        if( (double)(Math.Abs(e[m-1]))<=(double)(thresh) )
+                        {
+                            e[m-1] = 0;
+                        }
+                    }
+                    else
+                    {
+                        
+                        //
+                        // Chase bulge from bottom to top
+                        // Save cosines and sines for later singular vector updates
+                        //
+                        f = (Math.Abs(d[m])-shift)*(extsignbdsqr(1, d[m])+shift/d[m]);
+                        g = e[m-1];
+                        for(i=m; i>=ll+1; i--)
+                        {
+                            rotations.generaterotation(f, g, ref cosr, ref sinr, ref r);
+                            if( i<m )
+                            {
+                                e[i] = r;
+                            }
+                            f = cosr*d[i]+sinr*e[i-1];
+                            e[i-1] = cosr*e[i-1]-sinr*d[i];
+                            g = sinr*d[i-1];
+                            d[i-1] = cosr*d[i-1];
+                            rotations.generaterotation(f, g, ref cosl, ref sinl, ref r);
+                            d[i] = r;
+                            f = cosl*e[i-1]+sinl*d[i-1];
+                            d[i-1] = cosl*d[i-1]-sinl*e[i-1];
+                            if( i>ll+1 )
+                            {
+                                g = sinl*e[i-2];
+                                e[i-2] = cosl*e[i-2];
+                            }
+                            work0[i-ll] = cosr;
+                            work1[i-ll] = -sinr;
+                            work2[i-ll] = cosl;
+                            work3[i-ll] = -sinl;
+                        }
+                        e[ll] = f;
+                        
+                        //
+                        // Test convergence
+                        //
+                        if( (double)(Math.Abs(e[ll]))<=(double)(thresh) )
+                        {
+                            e[ll] = 0;
+                        }
+                        
+                        //
+                        // Update singular vectors if desired
+                        //
+                        if( ncvt>0 )
+                        {
+                            rotations.applyrotationsfromtheleft(!fwddir, ll+vstart-1, m+vstart-1, vstart, vend, work2, work3, ref vt, ref vttemp);
+                        }
+                        if( nru>0 )
+                        {
+                            rotations.applyrotationsfromtheright(!fwddir, ustart, uend, ll+ustart-1, m+ustart-1, work0, work1, ref u, ref utemp);
+                        }
+                        if( ncc>0 )
+                        {
+                            rotations.applyrotationsfromtheleft(!fwddir, ll+cstart-1, m+cstart-1, cstart, cend, work0, work1, ref c, ref ctemp);
+                        }
+                    }
+                }
+                
+                //
+                // QR iteration finished, go back and check convergence
+                //
+                continue;
+            }
+            
+            //
+            // All singular values converged, so make them positive
+            //
+            for(i=1; i<=n; i++)
+            {
+                if( (double)(d[i])<(double)(0) )
+                {
+                    d[i] = -d[i];
+                    
+                    //
+                    // Change sign of singular vectors, if desired
+                    //
+                    if( ncvt>0 )
+                    {
+                        for(i_=vstart; i_<=vend;i_++)
+                        {
+                            vt[i+vstart-1,i_] = -1*vt[i+vstart-1,i_];
+                        }
+                    }
+                }
+            }
+            
+            //
+            // Sort the singular values into decreasing order (insertion sort on
+            // singular values, but only one transposition per singular vector)
+            //
+            for(i=1; i<=n-1; i++)
+            {
+                
+                //
+                // Scan for smallest D(I)
+                //
+                isub = 1;
+                smin = d[1];
+                for(j=2; j<=n+1-i; j++)
+                {
+                    if( (double)(d[j])<=(double)(smin) )
+                    {
+                        isub = j;
+                        smin = d[j];
+                    }
+                }
+                if( isub!=n+1-i )
+                {
+                    
+                    //
+                    // Swap singular values and vectors
+                    //
+                    d[isub] = d[n+1-i];
+                    d[n+1-i] = smin;
+                    if( ncvt>0 )
+                    {
+                        j = n+1-i;
+                        for(i_=vstart; i_<=vend;i_++)
+                        {
+                            vttemp[i_] = vt[isub+vstart-1,i_];
+                        }
+                        for(i_=vstart; i_<=vend;i_++)
+                        {
+                            vt[isub+vstart-1,i_] = vt[j+vstart-1,i_];
+                        }
+                        for(i_=vstart; i_<=vend;i_++)
+                        {
+                            vt[j+vstart-1,i_] = vttemp[i_];
+                        }
+                    }
+                    if( nru>0 )
+                    {
+                        j = n+1-i;
+                        for(i_=ustart; i_<=uend;i_++)
+                        {
+                            utemp[i_] = u[i_,isub+ustart-1];
+                        }
+                        for(i_=ustart; i_<=uend;i_++)
+                        {
+                            u[i_,isub+ustart-1] = u[i_,j+ustart-1];
+                        }
+                        for(i_=ustart; i_<=uend;i_++)
+                        {
+                            u[i_,j+ustart-1] = utemp[i_];
+                        }
+                    }
+                    if( ncc>0 )
+                    {
+                        j = n+1-i;
+                        for(i_=cstart; i_<=cend;i_++)
+                        {
+                            ctemp[i_] = c[isub+cstart-1,i_];
+                        }
+                        for(i_=cstart; i_<=cend;i_++)
+                        {
+                            c[isub+cstart-1,i_] = c[j+cstart-1,i_];
+                        }
+                        for(i_=cstart; i_<=cend;i_++)
+                        {
+                            c[j+cstart-1,i_] = ctemp[i_];
+                        }
+                    }
+                }
+            }
+            return result;
+        }
+
+
+        private static double extsignbdsqr(double a,
+            double b)
+        {
+            double result = 0;
+
+            if( (double)(b)>=(double)(0) )
+            {
+                result = Math.Abs(a);
+            }
+            else
+            {
+                result = -Math.Abs(a);
+            }
+            return result;
+        }
+
+
+        private static void svd2x2(double f,
+            double g,
+            double h,
+            ref double ssmin,
+            ref double ssmax)
+        {
+            double aas = 0;
+            double at = 0;
+            double au = 0;
+            double c = 0;
+            double fa = 0;
+            double fhmn = 0;
+            double fhmx = 0;
+            double ga = 0;
+            double ha = 0;
+
+            ssmin = 0;
+            ssmax = 0;
+
+            fa = Math.Abs(f);
+            ga = Math.Abs(g);
+            ha = Math.Abs(h);
+            fhmn = Math.Min(fa, ha);
+            fhmx = Math.Max(fa, ha);
+            if( (double)(fhmn)==(double)(0) )
+            {
+                ssmin = 0;
+                if( (double)(fhmx)==(double)(0) )
+                {
+                    ssmax = ga;
+                }
+                else
+                {
+                    ssmax = Math.Max(fhmx, ga)*Math.Sqrt(1+math.sqr(Math.Min(fhmx, ga)/Math.Max(fhmx, ga)));
+                }
+            }
+            else
+            {
+                if( (double)(ga)<(double)(fhmx) )
+                {
+                    aas = 1+fhmn/fhmx;
+                    at = (fhmx-fhmn)/fhmx;
+                    au = math.sqr(ga/fhmx);
+                    c = 2/(Math.Sqrt(aas*aas+au)+Math.Sqrt(at*at+au));
+                    ssmin = fhmn*c;
+                    ssmax = fhmx/c;
+                }
+                else
+                {
+                    au = fhmx/ga;
+                    if( (double)(au)==(double)(0) )
+                    {
+                        
+                        //
+                        // Avoid possible harmful underflow if exponent range
+                        // asymmetric (true SSMIN may not underflow even if
+                        // AU underflows)
+                        //
+                        ssmin = fhmn*fhmx/ga;
+                        ssmax = ga;
+                    }
+                    else
+                    {
+                        aas = 1+fhmn/fhmx;
+                        at = (fhmx-fhmn)/fhmx;
+                        c = 1/(Math.Sqrt(1+math.sqr(aas*au))+Math.Sqrt(1+math.sqr(at*au)));
+                        ssmin = fhmn*c*au;
+                        ssmin = ssmin+ssmin;
+                        ssmax = ga/(c+c);
+                    }
+                }
+            }
+        }
+
+
+        private static void svdv2x2(double f,
+            double g,
+            double h,
+            ref double ssmin,
+            ref double ssmax,
+            ref double snr,
+            ref double csr,
+            ref double snl,
+            ref double csl)
+        {
+            bool gasmal = new bool();
+            bool swp = new bool();
+            int pmax = 0;
+            double a = 0;
+            double clt = 0;
+            double crt = 0;
+            double d = 0;
+            double fa = 0;
+            double ft = 0;
+            double ga = 0;
+            double gt = 0;
+            double ha = 0;
+            double ht = 0;
+            double l = 0;
+            double m = 0;
+            double mm = 0;
+            double r = 0;
+            double s = 0;
+            double slt = 0;
+            double srt = 0;
+            double t = 0;
+            double temp = 0;
+            double tsign = 0;
+            double tt = 0;
+            double v = 0;
+
+            ssmin = 0;
+            ssmax = 0;
+            snr = 0;
+            csr = 0;
+            snl = 0;
+            csl = 0;
+
+            ft = f;
+            fa = Math.Abs(ft);
+            ht = h;
+            ha = Math.Abs(h);
+            
+            //
+            // these initializers are not really necessary,
+            // but without them compiler complains about uninitialized locals
+            //
+            clt = 0;
+            crt = 0;
+            slt = 0;
+            srt = 0;
+            tsign = 0;
+            
+            //
+            // PMAX points to the maximum absolute element of matrix
+            //  PMAX = 1 if F largest in absolute values
+            //  PMAX = 2 if G largest in absolute values
+            //  PMAX = 3 if H largest in absolute values
+            //
+            pmax = 1;
+            swp = (double)(ha)>(double)(fa);
+            if( swp )
+            {
+                
+                //
+                // Now FA .ge. HA
+                //
+                pmax = 3;
+                temp = ft;
+                ft = ht;
+                ht = temp;
+                temp = fa;
+                fa = ha;
+                ha = temp;
+            }
+            gt = g;
+            ga = Math.Abs(gt);
+            if( (double)(ga)==(double)(0) )
+            {
+                
+                //
+                // Diagonal matrix
+                //
+                ssmin = ha;
+                ssmax = fa;
+                clt = 1;
+                crt = 1;
+                slt = 0;
+                srt = 0;
+            }
+            else
+            {
+                gasmal = true;
+                if( (double)(ga)>(double)(fa) )
+                {
+                    pmax = 2;
+                    if( (double)(fa/ga)<(double)(math.machineepsilon) )
+                    {
+                        
+                        //
+                        // Case of very large GA
+                        //
+                        gasmal = false;
+                        ssmax = ga;
+                        if( (double)(ha)>(double)(1) )
+                        {
+                            v = ga/ha;
+                            ssmin = fa/v;
+                        }
+                        else
+                        {
+                            v = fa/ga;
+                            ssmin = v*ha;
+                        }
+                        clt = 1;
+                        slt = ht/gt;
+                        srt = 1;
+                        crt = ft/gt;
+                    }
+                }
+                if( gasmal )
+                {
+                    
+                    //
+                    // Normal case
+                    //
+                    d = fa-ha;
+                    if( (double)(d)==(double)(fa) )
+                    {
+                        l = 1;
+                    }
+                    else
+                    {
+                        l = d/fa;
+                    }
+                    m = gt/ft;
+                    t = 2-l;
+                    mm = m*m;
+                    tt = t*t;
+                    s = Math.Sqrt(tt+mm);
+                    if( (double)(l)==(double)(0) )
+                    {
+                        r = Math.Abs(m);
+                    }
+                    else
+                    {
+                        r = Math.Sqrt(l*l+mm);
+                    }
+                    a = 0.5*(s+r);
+                    ssmin = ha/a;
+                    ssmax = fa*a;
+                    if( (double)(mm)==(double)(0) )
+                    {
+                        
+                        //
+                        // Note that M is very tiny
+                        //
+                        if( (double)(l)==(double)(0) )
+                        {
+                            t = extsignbdsqr(2, ft)*extsignbdsqr(1, gt);
+                        }
+                        else
+                        {
+                            t = gt/extsignbdsqr(d, ft)+m/t;
+                        }
+                    }
+                    else
+                    {
+                        t = (m/(s+t)+m/(r+l))*(1+a);
+                    }
+                    l = Math.Sqrt(t*t+4);
+                    crt = 2/l;
+                    srt = t/l;
+                    clt = (crt+srt*m)/a;
+                    v = ht/ft;
+                    slt = v*srt/a;
+                }
+            }
+            if( swp )
+            {
+                csl = srt;
+                snl = crt;
+                csr = slt;
+                snr = clt;
+            }
+            else
+            {
+                csl = clt;
+                snl = slt;
+                csr = crt;
+                snr = srt;
+            }
+            
+            //
+            // Correct signs of SSMAX and SSMIN
+            //
+            if( pmax==1 )
+            {
+                tsign = extsignbdsqr(1, csr)*extsignbdsqr(1, csl)*extsignbdsqr(1, f);
+            }
+            if( pmax==2 )
+            {
+                tsign = extsignbdsqr(1, snr)*extsignbdsqr(1, csl)*extsignbdsqr(1, g);
+            }
+            if( pmax==3 )
+            {
+                tsign = extsignbdsqr(1, snr)*extsignbdsqr(1, snl)*extsignbdsqr(1, h);
+            }
+            ssmax = extsignbdsqr(ssmax, tsign);
+            ssmin = extsignbdsqr(ssmin, tsign*extsignbdsqr(1, f)*extsignbdsqr(1, h));
+        }
+
+
+    }
+    public class svd
+    {
+        /*************************************************************************
+        Singular value decomposition of a rectangular matrix.
+
+        The algorithm calculates the singular value decomposition of a matrix of
+        size MxN: A = U * S * V^T
+
+        The algorithm finds the singular values and, optionally, matrices U and V^T.
+        The algorithm can find both first min(M,N) columns of matrix U and rows of
+        matrix V^T (singular vectors), and matrices U and V^T wholly (of sizes MxM
+        and NxN respectively).
+
+        Take into account that the subroutine does not return matrix V but V^T.
+
+        Input parameters:
+            A           -   matrix to be decomposed.
+                            Array whose indexes range within [0..M-1, 0..N-1].
+            M           -   number of rows in matrix A.
+            N           -   number of columns in matrix A.
+            UNeeded     -   0, 1 or 2. See the description of the parameter U.
+            VTNeeded    -   0, 1 or 2. See the description of the parameter VT.
+            AdditionalMemory -
+                            If the parameter:
+                             * equals 0, the algorithm doesnt use additional
+                               memory (lower requirements, lower performance).
+                             * equals 1, the algorithm uses additional
+                               memory of size min(M,N)*min(M,N) of real numbers.
+                               It often speeds up the algorithm.
+                             * equals 2, the algorithm uses additional
+                               memory of size M*min(M,N) of real numbers.
+                               It allows to get a maximum performance.
+                            The recommended value of the parameter is 2.
+
+        Output parameters:
+            W           -   contains singular values in descending order.
+            U           -   if UNeeded=0, U isn't changed, the left singular vectors
+                            are not calculated.
+                            if Uneeded=1, U contains left singular vectors (first
+                            min(M,N) columns of matrix U). Array whose indexes range
+                            within [0..M-1, 0..Min(M,N)-1].
+                            if UNeeded=2, U contains matrix U wholly. Array whose
+                            indexes range within [0..M-1, 0..M-1].
+            VT          -   if VTNeeded=0, VT isnt changed, the right singular vectors
+                            are not calculated.
+                            if VTNeeded=1, VT contains right singular vectors (first
+                            min(M,N) rows of matrix V^T). Array whose indexes range
+                            within [0..min(M,N)-1, 0..N-1].
+                            if VTNeeded=2, VT contains matrix V^T wholly. Array whose
+                            indexes range within [0..N-1, 0..N-1].
+
+          -- ALGLIB --
+             Copyright 2005 by Bochkanov Sergey
+        *************************************************************************/
+        public static bool rmatrixsvd(double[,] a,
+            int m,
+            int n,
+            int uneeded,
+            int vtneeded,
+            int additionalmemory,
+            ref double[] w,
+            ref double[,] u,
+            ref double[,] vt)
+        {
+            bool result = new bool();
+            double[] tauq = new double[0];
+            double[] taup = new double[0];
+            double[] tau = new double[0];
+            double[] e = new double[0];
+            double[] work = new double[0];
+            double[,] t2 = new double[0,0];
+            bool isupper = new bool();
+            int minmn = 0;
+            int ncu = 0;
+            int nrvt = 0;
+            int nru = 0;
+            int ncvt = 0;
+            int i = 0;
+            int j = 0;
+
+            a = (double[,])a.Clone();
+            w = new double[0];
+            u = new double[0,0];
+            vt = new double[0,0];
+
+            result = true;
+            if( m==0 || n==0 )
+            {
+                return result;
+            }
+            alglib.ap.assert(uneeded>=0 && uneeded<=2, "SVDDecomposition: wrong parameters!");
+            alglib.ap.assert(vtneeded>=0 && vtneeded<=2, "SVDDecomposition: wrong parameters!");
+            alglib.ap.assert(additionalmemory>=0 && additionalmemory<=2, "SVDDecomposition: wrong parameters!");
+            
+            //
+            // initialize
+            //
+            minmn = Math.Min(m, n);
+            w = new double[minmn+1];
+            ncu = 0;
+            nru = 0;
+            if( uneeded==1 )
+            {
+                nru = m;
+                ncu = minmn;
+                u = new double[nru-1+1, ncu-1+1];
+            }
+            if( uneeded==2 )
+            {
+                nru = m;
+                ncu = m;
+                u = new double[nru-1+1, ncu-1+1];
+            }
+            nrvt = 0;
+            ncvt = 0;
+            if( vtneeded==1 )
+            {
+                nrvt = minmn;
+                ncvt = n;
+                vt = new double[nrvt-1+1, ncvt-1+1];
+            }
+            if( vtneeded==2 )
+            {
+                nrvt = n;
+                ncvt = n;
+                vt = new double[nrvt-1+1, ncvt-1+1];
+            }
+            
+            //
+            // M much larger than N
+            // Use bidiagonal reduction with QR-decomposition
+            //
+            if( (double)(m)>(double)(1.6*n) )
+            {
+                if( uneeded==0 )
+                {
+                    
+                    //
+                    // No left singular vectors to be computed
+                    //
+                    ortfac.rmatrixqr(ref a, m, n, ref tau);
+                    for(i=0; i<=n-1; i++)
+                    {
+                        for(j=0; j<=i-1; j++)
+                        {
+                            a[i,j] = 0;
+                        }
+                    }
+                    ortfac.rmatrixbd(ref a, n, n, ref tauq, ref taup);
+                    ortfac.rmatrixbdunpackpt(a, n, n, taup, nrvt, ref vt);
+                    ortfac.rmatrixbdunpackdiagonals(a, n, n, ref isupper, ref w, ref e);
+                    result = bdsvd.rmatrixbdsvd(ref w, e, n, isupper, false, ref u, 0, ref a, 0, ref vt, ncvt);
+                    return result;
+                }
+                else
+                {
+                    
+                    //
+                    // Left singular vectors (may be full matrix U) to be computed
+                    //
+                    ortfac.rmatrixqr(ref a, m, n, ref tau);
+                    ortfac.rmatrixqrunpackq(a, m, n, tau, ncu, ref u);
+                    for(i=0; i<=n-1; i++)
+                    {
+                        for(j=0; j<=i-1; j++)
+                        {
+                            a[i,j] = 0;
+                        }
+                    }
+                    ortfac.rmatrixbd(ref a, n, n, ref tauq, ref taup);
+                    ortfac.rmatrixbdunpackpt(a, n, n, taup, nrvt, ref vt);
+                    ortfac.rmatrixbdunpackdiagonals(a, n, n, ref isupper, ref w, ref e);
+                    if( additionalmemory<1 )
+                    {
+                        
+                        //
+                        // No additional memory can be used
+                        //
+                        ortfac.rmatrixbdmultiplybyq(a, n, n, tauq, ref u, m, n, true, false);
+                        result = bdsvd.rmatrixbdsvd(ref w, e, n, isupper, false, ref u, m, ref a, 0, ref vt, ncvt);
+                    }
+                    else
+                    {
+                        
+                        //
+                        // Large U. Transforming intermediate matrix T2
+                        //
+                        work = new double[Math.Max(m, n)+1];
+                        ortfac.rmatrixbdunpackq(a, n, n, tauq, n, ref t2);
+                        blas.copymatrix(u, 0, m-1, 0, n-1, ref a, 0, m-1, 0, n-1);
+                        blas.inplacetranspose(ref t2, 0, n-1, 0, n-1, ref work);
+                        result = bdsvd.rmatrixbdsvd(ref w, e, n, isupper, false, ref u, 0, ref t2, n, ref vt, ncvt);
+                        blas.matrixmatrixmultiply(a, 0, m-1, 0, n-1, false, t2, 0, n-1, 0, n-1, true, 1.0, ref u, 0, m-1, 0, n-1, 0.0, ref work);
+                    }
+                    return result;
+                }
+            }
+            
+            //
+            // N much larger than M
+            // Use bidiagonal reduction with LQ-decomposition
+            //
+            if( (double)(n)>(double)(1.6*m) )
+            {
+                if( vtneeded==0 )
+                {
+                    
+                    //
+                    // No right singular vectors to be computed
+                    //
+                    ortfac.rmatrixlq(ref a, m, n, ref tau);
+                    for(i=0; i<=m-1; i++)
+                    {
+                        for(j=i+1; j<=m-1; j++)
+                        {
+                            a[i,j] = 0;
+                        }
+                    }
+                    ortfac.rmatrixbd(ref a, m, m, ref tauq, ref taup);
+                    ortfac.rmatrixbdunpackq(a, m, m, tauq, ncu, ref u);
+                    ortfac.rmatrixbdunpackdiagonals(a, m, m, ref isupper, ref w, ref e);
+                    work = new double[m+1];
+                    blas.inplacetranspose(ref u, 0, nru-1, 0, ncu-1, ref work);
+                    result = bdsvd.rmatrixbdsvd(ref w, e, m, isupper, false, ref a, 0, ref u, nru, ref vt, 0);
+                    blas.inplacetranspose(ref u, 0, nru-1, 0, ncu-1, ref work);
+                    return result;
+                }
+                else
+                {
+                    
+                    //
+                    // Right singular vectors (may be full matrix VT) to be computed
+                    //
+                    ortfac.rmatrixlq(ref a, m, n, ref tau);
+                    ortfac.rmatrixlqunpackq(a, m, n, tau, nrvt, ref vt);
+                    for(i=0; i<=m-1; i++)
+                    {
+                        for(j=i+1; j<=m-1; j++)
+                        {
+                            a[i,j] = 0;
+                        }
+                    }
+                    ortfac.rmatrixbd(ref a, m, m, ref tauq, ref taup);
+                    ortfac.rmatrixbdunpackq(a, m, m, tauq, ncu, ref u);
+                    ortfac.rmatrixbdunpackdiagonals(a, m, m, ref isupper, ref w, ref e);
+                    work = new double[Math.Max(m, n)+1];
+                    blas.inplacetranspose(ref u, 0, nru-1, 0, ncu-1, ref work);
+                    if( additionalmemory<1 )
+                    {
+                        
+                        //
+                        // No additional memory available
+                        //
+                        ortfac.rmatrixbdmultiplybyp(a, m, m, taup, ref vt, m, n, false, true);
+                        result = bdsvd.rmatrixbdsvd(ref w, e, m, isupper, false, ref a, 0, ref u, nru, ref vt, n);
+                    }
+                    else
+                    {
+                        
+                        //
+                        // Large VT. Transforming intermediate matrix T2
+                        //
+                        ortfac.rmatrixbdunpackpt(a, m, m, taup, m, ref t2);
+                        result = bdsvd.rmatrixbdsvd(ref w, e, m, isupper, false, ref a, 0, ref u, nru, ref t2, m);
+                        blas.copymatrix(vt, 0, m-1, 0, n-1, ref a, 0, m-1, 0, n-1);
+                        blas.matrixmatrixmultiply(t2, 0, m-1, 0, m-1, false, a, 0, m-1, 0, n-1, false, 1.0, ref vt, 0, m-1, 0, n-1, 0.0, ref work);
+                    }
+                    blas.inplacetranspose(ref u, 0, nru-1, 0, ncu-1, ref work);
+                    return result;
+                }
+            }
+            
+            //
+            // M<=N
+            // We can use inplace transposition of U to get rid of columnwise operations
+            //
+            if( m<=n )
+            {
+                ortfac.rmatrixbd(ref a, m, n, ref tauq, ref taup);
+                ortfac.rmatrixbdunpackq(a, m, n, tauq, ncu, ref u);
+                ortfac.rmatrixbdunpackpt(a, m, n, taup, nrvt, ref vt);
+                ortfac.rmatrixbdunpackdiagonals(a, m, n, ref isupper, ref w, ref e);
+                work = new double[m+1];
+                blas.inplacetranspose(ref u, 0, nru-1, 0, ncu-1, ref work);
+                result = bdsvd.rmatrixbdsvd(ref w, e, minmn, isupper, false, ref a, 0, ref u, nru, ref vt, ncvt);
+                blas.inplacetranspose(ref u, 0, nru-1, 0, ncu-1, ref work);
+                return result;
+            }
+            
+            //
+            // Simple bidiagonal reduction
+            //
+            ortfac.rmatrixbd(ref a, m, n, ref tauq, ref taup);
+            ortfac.rmatrixbdunpackq(a, m, n, tauq, ncu, ref u);
+            ortfac.rmatrixbdunpackpt(a, m, n, taup, nrvt, ref vt);
+            ortfac.rmatrixbdunpackdiagonals(a, m, n, ref isupper, ref w, ref e);
+            if( additionalmemory<2 || uneeded==0 )
+            {
+                
+                //
+                // We cant use additional memory or there is no need in such operations
+                //
+                result = bdsvd.rmatrixbdsvd(ref w, e, minmn, isupper, false, ref u, nru, ref a, 0, ref vt, ncvt);
+            }
+            else
+            {
+                
+                //
+                // We can use additional memory
+                //
+                t2 = new double[minmn-1+1, m-1+1];
+                blas.copyandtranspose(u, 0, m-1, 0, minmn-1, ref t2, 0, minmn-1, 0, m-1);
+                result = bdsvd.rmatrixbdsvd(ref w, e, minmn, isupper, false, ref u, 0, ref t2, m, ref vt, ncvt);
+                blas.copyandtranspose(t2, 0, minmn-1, 0, m-1, ref u, 0, m-1, 0, minmn-1);
+            }
+            return result;
+        }
+
+
+    }
+    public class evd
+    {
+        /*************************************************************************
+        Finding the eigenvalues and eigenvectors of a symmetric matrix
+
+        The algorithm finds eigen pairs of a symmetric matrix by reducing it to
+        tridiagonal form and using the QL/QR algorithm.
+
+        Input parameters:
+            A       -   symmetric matrix which is given by its upper or lower
+                        triangular part.
+                        Array whose indexes range within [0..N-1, 0..N-1].
+            N       -   size of matrix A.
+            ZNeeded -   flag controlling whether the eigenvectors are needed or not.
+                        If ZNeeded is equal to:
+                         * 0, the eigenvectors are not returned;
+                         * 1, the eigenvectors are returned.
+            IsUpper -   storage format.
+
+        Output parameters:
+            D       -   eigenvalues in ascending order.
+                        Array whose index ranges within [0..N-1].
+            Z       -   if ZNeeded is equal to:
+                         * 0, Z hasnt changed;
+                         * 1, Z contains the eigenvectors.
+                        Array whose indexes range within [0..N-1, 0..N-1].
+                        The eigenvectors are stored in the matrix columns.
+
+        Result:
+            True, if the algorithm has converged.
+            False, if the algorithm hasn't converged (rare case).
+
+          -- ALGLIB --
+             Copyright 2005-2008 by Bochkanov Sergey
+        *************************************************************************/
+        public static bool smatrixevd(double[,] a,
+            int n,
+            int zneeded,
+            bool isupper,
+            ref double[] d,
+            ref double[,] z)
+        {
+            bool result = new bool();
+            double[] tau = new double[0];
+            double[] e = new double[0];
+
+            a = (double[,])a.Clone();
+            d = new double[0];
+            z = new double[0,0];
+
+            alglib.ap.assert(zneeded==0 || zneeded==1, "SMatrixEVD: incorrect ZNeeded");
+            ortfac.smatrixtd(ref a, n, isupper, ref tau, ref d, ref e);
+            if( zneeded==1 )
+            {
+                ortfac.smatrixtdunpackq(a, n, isupper, tau, ref z);
+            }
+            result = smatrixtdevd(ref d, e, n, zneeded, ref z);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Subroutine for finding the eigenvalues (and eigenvectors) of  a  symmetric
+        matrix  in  a  given half open interval (A, B] by using  a  bisection  and
+        inverse iteration
+
+        Input parameters:
+            A       -   symmetric matrix which is given by its upper or lower
+                        triangular part. Array [0..N-1, 0..N-1].
+            N       -   size of matrix A.
+            ZNeeded -   flag controlling whether the eigenvectors are needed or not.
+                        If ZNeeded is equal to:
+                         * 0, the eigenvectors are not returned;
+                         * 1, the eigenvectors are returned.
+            IsUpperA -  storage format of matrix A.
+            B1, B2 -    half open interval (B1, B2] to search eigenvalues in.
+
+        Output parameters:
+            M       -   number of eigenvalues found in a given half-interval (M>=0).
+            W       -   array of the eigenvalues found.
+                        Array whose index ranges within [0..M-1].
+            Z       -   if ZNeeded is equal to:
+                         * 0, Z hasnt changed;
+                         * 1, Z contains eigenvectors.
+                        Array whose indexes range within [0..N-1, 0..M-1].
+                        The eigenvectors are stored in the matrix columns.
+
+        Result:
+            True, if successful. M contains the number of eigenvalues in the given
+            half-interval (could be equal to 0), W contains the eigenvalues,
+            Z contains the eigenvectors (if needed).
+
+            False, if the bisection method subroutine wasn't able to find the
+            eigenvalues in the given interval or if the inverse iteration subroutine
+            wasn't able to find all the corresponding eigenvectors.
+            In that case, the eigenvalues and eigenvectors are not returned,
+            M is equal to 0.
+
+          -- ALGLIB --
+             Copyright 07.01.2006 by Bochkanov Sergey
+        *************************************************************************/
+        public static bool smatrixevdr(double[,] a,
+            int n,
+            int zneeded,
+            bool isupper,
+            double b1,
+            double b2,
+            ref int m,
+            ref double[] w,
+            ref double[,] z)
+        {
+            bool result = new bool();
+            double[] tau = new double[0];
+            double[] e = new double[0];
+
+            a = (double[,])a.Clone();
+            m = 0;
+            w = new double[0];
+            z = new double[0,0];
+
+            alglib.ap.assert(zneeded==0 || zneeded==1, "SMatrixTDEVDR: incorrect ZNeeded");
+            ortfac.smatrixtd(ref a, n, isupper, ref tau, ref w, ref e);
+            if( zneeded==1 )
+            {
+                ortfac.smatrixtdunpackq(a, n, isupper, tau, ref z);
+            }
+            result = smatrixtdevdr(ref w, e, n, zneeded, b1, b2, ref m, ref z);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Subroutine for finding the eigenvalues and  eigenvectors  of  a  symmetric
+        matrix with given indexes by using bisection and inverse iteration methods.
+
+        Input parameters:
+            A       -   symmetric matrix which is given by its upper or lower
+                        triangular part. Array whose indexes range within [0..N-1, 0..N-1].
+            N       -   size of matrix A.
+            ZNeeded -   flag controlling whether the eigenvectors are needed or not.
+                        If ZNeeded is equal to:
+                         * 0, the eigenvectors are not returned;
+                         * 1, the eigenvectors are returned.
+            IsUpperA -  storage format of matrix A.
+            I1, I2 -    index interval for searching (from I1 to I2).
+                        0 <= I1 <= I2 <= N-1.
+
+        Output parameters:
+            W       -   array of the eigenvalues found.
+                        Array whose index ranges within [0..I2-I1].
+            Z       -   if ZNeeded is equal to:
+                         * 0, Z hasnt changed;
+                         * 1, Z contains eigenvectors.
+                        Array whose indexes range within [0..N-1, 0..I2-I1].
+                        In that case, the eigenvectors are stored in the matrix columns.
+
+        Result:
+            True, if successful. W contains the eigenvalues, Z contains the
+            eigenvectors (if needed).
+
+            False, if the bisection method subroutine wasn't able to find the
+            eigenvalues in the given interval or if the inverse iteration subroutine
+            wasn't able to find all the corresponding eigenvectors.
+            In that case, the eigenvalues and eigenvectors are not returned.
+
+          -- ALGLIB --
+             Copyright 07.01.2006 by Bochkanov Sergey
+        *************************************************************************/
+        public static bool smatrixevdi(double[,] a,
+            int n,
+            int zneeded,
+            bool isupper,
+            int i1,
+            int i2,
+            ref double[] w,
+            ref double[,] z)
+        {
+            bool result = new bool();
+            double[] tau = new double[0];
+            double[] e = new double[0];
+
+            a = (double[,])a.Clone();
+            w = new double[0];
+            z = new double[0,0];
+
+            alglib.ap.assert(zneeded==0 || zneeded==1, "SMatrixEVDI: incorrect ZNeeded");
+            ortfac.smatrixtd(ref a, n, isupper, ref tau, ref w, ref e);
+            if( zneeded==1 )
+            {
+                ortfac.smatrixtdunpackq(a, n, isupper, tau, ref z);
+            }
+            result = smatrixtdevdi(ref w, e, n, zneeded, i1, i2, ref z);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Finding the eigenvalues and eigenvectors of a Hermitian matrix
+
+        The algorithm finds eigen pairs of a Hermitian matrix by  reducing  it  to
+        real tridiagonal form and using the QL/QR algorithm.
+
+        Input parameters:
+            A       -   Hermitian matrix which is given  by  its  upper  or  lower
+                        triangular part.
+                        Array whose indexes range within [0..N-1, 0..N-1].
+            N       -   size of matrix A.
+            IsUpper -   storage format.
+            ZNeeded -   flag controlling whether the eigenvectors  are  needed  or
+                        not. If ZNeeded is equal to:
+                         * 0, the eigenvectors are not returned;
+                         * 1, the eigenvectors are returned.
+
+        Output parameters:
+            D       -   eigenvalues in ascending order.
+                        Array whose index ranges within [0..N-1].
+            Z       -   if ZNeeded is equal to:
+                         * 0, Z hasnt changed;
+                         * 1, Z contains the eigenvectors.
+                        Array whose indexes range within [0..N-1, 0..N-1].
+                        The eigenvectors are stored in the matrix columns.
+
+        Result:
+            True, if the algorithm has converged.
+            False, if the algorithm hasn't converged (rare case).
+
+        Note:
+            eigenvectors of Hermitian matrix are defined up to  multiplication  by
+            a complex number L, such that |L|=1.
+
+          -- ALGLIB --
+             Copyright 2005, 23 March 2007 by Bochkanov Sergey
+        *************************************************************************/
+        public static bool hmatrixevd(complex[,] a,
+            int n,
+            int zneeded,
+            bool isupper,
+            ref double[] d,
+            ref complex[,] z)
+        {
+            bool result = new bool();
+            complex[] tau = new complex[0];
+            double[] e = new double[0];
+            double[] work = new double[0];
+            double[,] t = new double[0,0];
+            complex[,] q = new complex[0,0];
+            int i = 0;
+            int k = 0;
+            double v = 0;
+            int i_ = 0;
+
+            a = (complex[,])a.Clone();
+            d = new double[0];
+            z = new complex[0,0];
+
+            alglib.ap.assert(zneeded==0 || zneeded==1, "HermitianEVD: incorrect ZNeeded");
+            
+            //
+            // Reduce to tridiagonal form
+            //
+            ortfac.hmatrixtd(ref a, n, isupper, ref tau, ref d, ref e);
+            if( zneeded==1 )
+            {
+                ortfac.hmatrixtdunpackq(a, n, isupper, tau, ref q);
+                zneeded = 2;
+            }
+            
+            //
+            // TDEVD
+            //
+            result = smatrixtdevd(ref d, e, n, zneeded, ref t);
+            
+            //
+            // Eigenvectors are needed
+            // Calculate Z = Q*T = Re(Q)*T + i*Im(Q)*T
+            //
+            if( result && zneeded!=0 )
+            {
+                work = new double[n-1+1];
+                z = new complex[n-1+1, n-1+1];
+                for(i=0; i<=n-1; i++)
+                {
+                    
+                    //
+                    // Calculate real part
+                    //
+                    for(k=0; k<=n-1; k++)
+                    {
+                        work[k] = 0;
+                    }
+                    for(k=0; k<=n-1; k++)
+                    {
+                        v = q[i,k].x;
+                        for(i_=0; i_<=n-1;i_++)
+                        {
+                            work[i_] = work[i_] + v*t[k,i_];
+                        }
+                    }
+                    for(k=0; k<=n-1; k++)
+                    {
+                        z[i,k].x = work[k];
+                    }
+                    
+                    //
+                    // Calculate imaginary part
+                    //
+                    for(k=0; k<=n-1; k++)
+                    {
+                        work[k] = 0;
+                    }
+                    for(k=0; k<=n-1; k++)
+                    {
+                        v = q[i,k].y;
+                        for(i_=0; i_<=n-1;i_++)
+                        {
+                            work[i_] = work[i_] + v*t[k,i_];
+                        }
+                    }
+                    for(k=0; k<=n-1; k++)
+                    {
+                        z[i,k].y = work[k];
+                    }
+                }
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        Subroutine for finding the eigenvalues (and eigenvectors) of  a  Hermitian
+        matrix  in  a  given half-interval (A, B] by using a bisection and inverse
+        iteration
+
+        Input parameters:
+            A       -   Hermitian matrix which is given  by  its  upper  or  lower
+                        triangular  part.  Array  whose   indexes   range   within
+                        [0..N-1, 0..N-1].
+            N       -   size of matrix A.
+            ZNeeded -   flag controlling whether the eigenvectors  are  needed  or
+                        not. If ZNeeded is equal to:
+                         * 0, the eigenvectors are not returned;
+                         * 1, the eigenvectors are returned.
+            IsUpperA -  storage format of matrix A.
+            B1, B2 -    half-interval (B1, B2] to search eigenvalues in.
+
+        Output parameters:
+            M       -   number of eigenvalues found in a given half-interval, M>=0
+            W       -   array of the eigenvalues found.
+                        Array whose index ranges within [0..M-1].
+            Z       -   if ZNeeded is equal to:
+                         * 0, Z hasnt changed;
+                         * 1, Z contains eigenvectors.
+                        Array whose indexes range within [0..N-1, 0..M-1].
+                        The eigenvectors are stored in the matrix columns.
+
+        Result:
+            True, if successful. M contains the number of eigenvalues in the given
+            half-interval (could be equal to 0), W contains the eigenvalues,
+            Z contains the eigenvectors (if needed).
+
+            False, if the bisection method subroutine  wasn't  able  to  find  the
+            eigenvalues  in  the  given  interval  or  if  the  inverse  iteration
+            subroutine  wasn't  able  to  find all the corresponding eigenvectors.
+            In that case, the eigenvalues and eigenvectors are not returned, M  is
+            equal to 0.
+
+        Note:
+            eigen vectors of Hermitian matrix are defined up to multiplication  by
+            a complex number L, such as |L|=1.
+
+          -- ALGLIB --
+             Copyright 07.01.2006, 24.03.2007 by Bochkanov Sergey.
+        *************************************************************************/
+        public static bool hmatrixevdr(complex[,] a,
+            int n,
+            int zneeded,
+            bool isupper,
+            double b1,
+            double b2,
+            ref int m,
+            ref double[] w,
+            ref complex[,] z)
+        {
+            bool result = new bool();
+            complex[,] q = new complex[0,0];
+            double[,] t = new double[0,0];
+            complex[] tau = new complex[0];
+            double[] e = new double[0];
+            double[] work = new double[0];
+            int i = 0;
+            int k = 0;
+            double v = 0;
+            int i_ = 0;
+
+            a = (complex[,])a.Clone();
+            m = 0;
+            w = new double[0];
+            z = new complex[0,0];
+
+            alglib.ap.assert(zneeded==0 || zneeded==1, "HermitianEigenValuesAndVectorsInInterval: incorrect ZNeeded");
+            
+            //
+            // Reduce to tridiagonal form
+            //
+            ortfac.hmatrixtd(ref a, n, isupper, ref tau, ref w, ref e);
+            if( zneeded==1 )
+            {
+                ortfac.hmatrixtdunpackq(a, n, isupper, tau, ref q);
+                zneeded = 2;
+            }
+            
+            //
+            // Bisection and inverse iteration
+            //
+            result = smatrixtdevdr(ref w, e, n, zneeded, b1, b2, ref m, ref t);
+            
+            //
+            // Eigenvectors are needed
+            // Calculate Z = Q*T = Re(Q)*T + i*Im(Q)*T
+            //
+            if( (result && zneeded!=0) && m!=0 )
+            {
+                work = new double[m-1+1];
+                z = new complex[n-1+1, m-1+1];
+                for(i=0; i<=n-1; i++)
+                {
+                    
+                    //
+                    // Calculate real part
+                    //
+                    for(k=0; k<=m-1; k++)
+                    {
+                        work[k] = 0;
+                    }
+                    for(k=0; k<=n-1; k++)
+                    {
+                        v = q[i,k].x;
+                        for(i_=0; i_<=m-1;i_++)
+                        {
+                            work[i_] = work[i_] + v*t[k,i_];
+                        }
+                    }
+                    for(k=0; k<=m-1; k++)
+                    {
+                        z[i,k].x = work[k];
+                    }
+                    
+                    //
+                    // Calculate imaginary part
+                    //
+                    for(k=0; k<=m-1; k++)
+                    {
+                        work[k] = 0;
+                    }
+                    for(k=0; k<=n-1; k++)
+                    {
+                        v = q[i,k].y;
+                        for(i_=0; i_<=m-1;i_++)
+                        {
+                            work[i_] = work[i_] + v*t[k,i_];
+                        }
+                    }
+                    for(k=0; k<=m-1; k++)
+                    {
+                        z[i,k].y = work[k];
+                    }
+                }
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        Subroutine for finding the eigenvalues and  eigenvectors  of  a  Hermitian
+        matrix with given indexes by using bisection and inverse iteration methods
+
+        Input parameters:
+            A       -   Hermitian matrix which is given  by  its  upper  or  lower
+                        triangular part.
+                        Array whose indexes range within [0..N-1, 0..N-1].
+            N       -   size of matrix A.
+            ZNeeded -   flag controlling whether the eigenvectors  are  needed  or
+                        not. If ZNeeded is equal to:
+                         * 0, the eigenvectors are not returned;
+                         * 1, the eigenvectors are returned.
+            IsUpperA -  storage format of matrix A.
+            I1, I2 -    index interval for searching (from I1 to I2).
+                        0 <= I1 <= I2 <= N-1.
+
+        Output parameters:
+            W       -   array of the eigenvalues found.
+                        Array whose index ranges within [0..I2-I1].
+            Z       -   if ZNeeded is equal to:
+                         * 0, Z hasnt changed;
+                         * 1, Z contains eigenvectors.
+                        Array whose indexes range within [0..N-1, 0..I2-I1].
+                        In  that  case,  the eigenvectors are stored in the matrix
+                        columns.
+
+        Result:
+            True, if successful. W contains the eigenvalues, Z contains the
+            eigenvectors (if needed).
+
+            False, if the bisection method subroutine  wasn't  able  to  find  the
+            eigenvalues  in  the  given  interval  or  if  the  inverse  iteration
+            subroutine wasn't able to find  all  the  corresponding  eigenvectors.
+            In that case, the eigenvalues and eigenvectors are not returned.
+
+        Note:
+            eigen vectors of Hermitian matrix are defined up to multiplication  by
+            a complex number L, such as |L|=1.
+
+          -- ALGLIB --
+             Copyright 07.01.2006, 24.03.2007 by Bochkanov Sergey.
+        *************************************************************************/
+        public static bool hmatrixevdi(complex[,] a,
+            int n,
+            int zneeded,
+            bool isupper,
+            int i1,
+            int i2,
+            ref double[] w,
+            ref complex[,] z)
+        {
+            bool result = new bool();
+            complex[,] q = new complex[0,0];
+            double[,] t = new double[0,0];
+            complex[] tau = new complex[0];
+            double[] e = new double[0];
+            double[] work = new double[0];
+            int i = 0;
+            int k = 0;
+            double v = 0;
+            int m = 0;
+            int i_ = 0;
+
+            a = (complex[,])a.Clone();
+            w = new double[0];
+            z = new complex[0,0];
+
+            alglib.ap.assert(zneeded==0 || zneeded==1, "HermitianEigenValuesAndVectorsByIndexes: incorrect ZNeeded");
+            
+            //
+            // Reduce to tridiagonal form
+            //
+            ortfac.hmatrixtd(ref a, n, isupper, ref tau, ref w, ref e);
+            if( zneeded==1 )
+            {
+                ortfac.hmatrixtdunpackq(a, n, isupper, tau, ref q);
+                zneeded = 2;
+            }
+            
+            //
+            // Bisection and inverse iteration
+            //
+            result = smatrixtdevdi(ref w, e, n, zneeded, i1, i2, ref t);
+            
+            //
+            // Eigenvectors are needed
+            // Calculate Z = Q*T = Re(Q)*T + i*Im(Q)*T
+            //
+            m = i2-i1+1;
+            if( result && zneeded!=0 )
+            {
+                work = new double[m-1+1];
+                z = new complex[n-1+1, m-1+1];
+                for(i=0; i<=n-1; i++)
+                {
+                    
+                    //
+                    // Calculate real part
+                    //
+                    for(k=0; k<=m-1; k++)
+                    {
+                        work[k] = 0;
+                    }
+                    for(k=0; k<=n-1; k++)
+                    {
+                        v = q[i,k].x;
+                        for(i_=0; i_<=m-1;i_++)
+                        {
+                            work[i_] = work[i_] + v*t[k,i_];
+                        }
+                    }
+                    for(k=0; k<=m-1; k++)
+                    {
+                        z[i,k].x = work[k];
+                    }
+                    
+                    //
+                    // Calculate imaginary part
+                    //
+                    for(k=0; k<=m-1; k++)
+                    {
+                        work[k] = 0;
+                    }
+                    for(k=0; k<=n-1; k++)
+                    {
+                        v = q[i,k].y;
+                        for(i_=0; i_<=m-1;i_++)
+                        {
+                            work[i_] = work[i_] + v*t[k,i_];
+                        }
+                    }
+                    for(k=0; k<=m-1; k++)
+                    {
+                        z[i,k].y = work[k];
+                    }
+                }
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        Finding the eigenvalues and eigenvectors of a tridiagonal symmetric matrix
+
+        The algorithm finds the eigen pairs of a tridiagonal symmetric matrix by
+        using an QL/QR algorithm with implicit shifts.
+
+        Input parameters:
+            D       -   the main diagonal of a tridiagonal matrix.
+                        Array whose index ranges within [0..N-1].
+            E       -   the secondary diagonal of a tridiagonal matrix.
+                        Array whose index ranges within [0..N-2].
+            N       -   size of matrix A.
+            ZNeeded -   flag controlling whether the eigenvectors are needed or not.
+                        If ZNeeded is equal to:
+                         * 0, the eigenvectors are not needed;
+                         * 1, the eigenvectors of a tridiagonal matrix
+                           are multiplied by the square matrix Z. It is used if the
+                           tridiagonal matrix is obtained by the similarity
+                           transformation of a symmetric matrix;
+                         * 2, the eigenvectors of a tridiagonal matrix replace the
+                           square matrix Z;
+                         * 3, matrix Z contains the first row of the eigenvectors
+                           matrix.
+            Z       -   if ZNeeded=1, Z contains the square matrix by which the
+                        eigenvectors are multiplied.
+                        Array whose indexes range within [0..N-1, 0..N-1].
+
+        Output parameters:
+            D       -   eigenvalues in ascending order.
+                        Array whose index ranges within [0..N-1].
+            Z       -   if ZNeeded is equal to:
+                         * 0, Z hasnt changed;
+                         * 1, Z contains the product of a given matrix (from the left)
+                           and the eigenvectors matrix (from the right);
+                         * 2, Z contains the eigenvectors.
+                         * 3, Z contains the first row of the eigenvectors matrix.
+                        If ZNeeded<3, Z is the array whose indexes range within [0..N-1, 0..N-1].
+                        In that case, the eigenvectors are stored in the matrix columns.
+                        If ZNeeded=3, Z is the array whose indexes range within [0..0, 0..N-1].
+
+        Result:
+            True, if the algorithm has converged.
+            False, if the algorithm hasn't converged.
+
+          -- LAPACK routine (version 3.0) --
+             Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd.,
+             Courant Institute, Argonne National Lab, and Rice University
+             September 30, 1994
+        *************************************************************************/
+        public static bool smatrixtdevd(ref double[] d,
+            double[] e,
+            int n,
+            int zneeded,
+            ref double[,] z)
+        {
+            bool result = new bool();
+            double[] d1 = new double[0];
+            double[] e1 = new double[0];
+            double[,] z1 = new double[0,0];
+            int i = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            e = (double[])e.Clone();
+
+            
+            //
+            // Prepare 1-based task
+            //
+            d1 = new double[n+1];
+            e1 = new double[n+1];
+            i1_ = (0) - (1);
+            for(i_=1; i_<=n;i_++)
+            {
+                d1[i_] = d[i_+i1_];
+            }
+            if( n>1 )
+            {
+                i1_ = (0) - (1);
+                for(i_=1; i_<=n-1;i_++)
+                {
+                    e1[i_] = e[i_+i1_];
+                }
+            }
+            if( zneeded==1 )
+            {
+                z1 = new double[n+1, n+1];
+                for(i=1; i<=n; i++)
+                {
+                    i1_ = (0) - (1);
+                    for(i_=1; i_<=n;i_++)
+                    {
+                        z1[i,i_] = z[i-1,i_+i1_];
+                    }
+                }
+            }
+            
+            //
+            // Solve 1-based task
+            //
+            result = tridiagonalevd(ref d1, e1, n, zneeded, ref z1);
+            if( !result )
+            {
+                return result;
+            }
+            
+            //
+            // Convert back to 0-based result
+            //
+            i1_ = (1) - (0);
+            for(i_=0; i_<=n-1;i_++)
+            {
+                d[i_] = d1[i_+i1_];
+            }
+            if( zneeded!=0 )
+            {
+                if( zneeded==1 )
+                {
+                    for(i=1; i<=n; i++)
+                    {
+                        i1_ = (1) - (0);
+                        for(i_=0; i_<=n-1;i_++)
+                        {
+                            z[i-1,i_] = z1[i,i_+i1_];
+                        }
+                    }
+                    return result;
+                }
+                if( zneeded==2 )
+                {
+                    z = new double[n-1+1, n-1+1];
+                    for(i=1; i<=n; i++)
+                    {
+                        i1_ = (1) - (0);
+                        for(i_=0; i_<=n-1;i_++)
+                        {
+                            z[i-1,i_] = z1[i,i_+i1_];
+                        }
+                    }
+                    return result;
+                }
+                if( zneeded==3 )
+                {
+                    z = new double[0+1, n-1+1];
+                    i1_ = (1) - (0);
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        z[0,i_] = z1[1,i_+i1_];
+                    }
+                    return result;
+                }
+                alglib.ap.assert(false, "SMatrixTDEVD: Incorrect ZNeeded!");
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        Subroutine for finding the tridiagonal matrix eigenvalues/vectors in a
+        given half-interval (A, B] by using bisection and inverse iteration.
+
+        Input parameters:
+            D       -   the main diagonal of a tridiagonal matrix.
+                        Array whose index ranges within [0..N-1].
+            E       -   the secondary diagonal of a tridiagonal matrix.
+                        Array whose index ranges within [0..N-2].
+            N       -   size of matrix, N>=0.
+            ZNeeded -   flag controlling whether the eigenvectors are needed or not.
+                        If ZNeeded is equal to:
+                         * 0, the eigenvectors are not needed;
+                         * 1, the eigenvectors of a tridiagonal matrix are multiplied
+                           by the square matrix Z. It is used if the tridiagonal
+                           matrix is obtained by the similarity transformation
+                           of a symmetric matrix.
+                         * 2, the eigenvectors of a tridiagonal matrix replace matrix Z.
+            A, B    -   half-interval (A, B] to search eigenvalues in.
+            Z       -   if ZNeeded is equal to:
+                         * 0, Z isn't used and remains unchanged;
+                         * 1, Z contains the square matrix (array whose indexes range
+                           within [0..N-1, 0..N-1]) which reduces the given symmetric
+                           matrix to tridiagonal form;
+                         * 2, Z isn't used (but changed on the exit).
+
+        Output parameters:
+            D       -   array of the eigenvalues found.
+                        Array whose index ranges within [0..M-1].
+            M       -   number of eigenvalues found in the given half-interval (M>=0).
+            Z       -   if ZNeeded is equal to:
+                         * 0, doesn't contain any information;
+                         * 1, contains the product of a given NxN matrix Z (from the
+                           left) and NxM matrix of the eigenvectors found (from the
+                           right). Array whose indexes range within [0..N-1, 0..M-1].
+                         * 2, contains the matrix of the eigenvectors found.
+                           Array whose indexes range within [0..N-1, 0..M-1].
+
+        Result:
+
+            True, if successful. In that case, M contains the number of eigenvalues
+            in the given half-interval (could be equal to 0), D contains the eigenvalues,
+            Z contains the eigenvectors (if needed).
+            It should be noted that the subroutine changes the size of arrays D and Z.
+
+            False, if the bisection method subroutine wasn't able to find the
+            eigenvalues in the given interval or if the inverse iteration subroutine
+            wasn't able to find all the corresponding eigenvectors. In that case,
+            the eigenvalues and eigenvectors are not returned, M is equal to 0.
+
+          -- ALGLIB --
+             Copyright 31.03.2008 by Bochkanov Sergey
+        *************************************************************************/
+        public static bool smatrixtdevdr(ref double[] d,
+            double[] e,
+            int n,
+            int zneeded,
+            double a,
+            double b,
+            ref int m,
+            ref double[,] z)
+        {
+            bool result = new bool();
+            int errorcode = 0;
+            int nsplit = 0;
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int cr = 0;
+            int[] iblock = new int[0];
+            int[] isplit = new int[0];
+            int[] ifail = new int[0];
+            double[] d1 = new double[0];
+            double[] e1 = new double[0];
+            double[] w = new double[0];
+            double[,] z2 = new double[0,0];
+            double[,] z3 = new double[0,0];
+            double v = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            m = 0;
+
+            alglib.ap.assert(zneeded>=0 && zneeded<=2, "SMatrixTDEVDR: incorrect ZNeeded!");
+            
+            //
+            // Special cases
+            //
+            if( (double)(b)<=(double)(a) )
+            {
+                m = 0;
+                result = true;
+                return result;
+            }
+            if( n<=0 )
+            {
+                m = 0;
+                result = true;
+                return result;
+            }
+            
+            //
+            // Copy D,E to D1, E1
+            //
+            d1 = new double[n+1];
+            i1_ = (0) - (1);
+            for(i_=1; i_<=n;i_++)
+            {
+                d1[i_] = d[i_+i1_];
+            }
+            if( n>1 )
+            {
+                e1 = new double[n-1+1];
+                i1_ = (0) - (1);
+                for(i_=1; i_<=n-1;i_++)
+                {
+                    e1[i_] = e[i_+i1_];
+                }
+            }
+            
+            //
+            // No eigen vectors
+            //
+            if( zneeded==0 )
+            {
+                result = internalbisectioneigenvalues(d1, e1, n, 2, 1, a, b, 0, 0, -1, ref w, ref m, ref nsplit, ref iblock, ref isplit, ref errorcode);
+                if( !result || m==0 )
+                {
+                    m = 0;
+                    return result;
+                }
+                d = new double[m-1+1];
+                i1_ = (1) - (0);
+                for(i_=0; i_<=m-1;i_++)
+                {
+                    d[i_] = w[i_+i1_];
+                }
+                return result;
+            }
+            
+            //
+            // Eigen vectors are multiplied by Z
+            //
+            if( zneeded==1 )
+            {
+                
+                //
+                // Find eigen pairs
+                //
+                result = internalbisectioneigenvalues(d1, e1, n, 2, 2, a, b, 0, 0, -1, ref w, ref m, ref nsplit, ref iblock, ref isplit, ref errorcode);
+                if( !result || m==0 )
+                {
+                    m = 0;
+                    return result;
+                }
+                internaldstein(n, d1, e1, m, w, iblock, isplit, ref z2, ref ifail, ref cr);
+                if( cr!=0 )
+                {
+                    m = 0;
+                    result = false;
+                    return result;
+                }
+                
+                //
+                // Sort eigen values and vectors
+                //
+                for(i=1; i<=m; i++)
+                {
+                    k = i;
+                    for(j=i; j<=m; j++)
+                    {
+                        if( (double)(w[j])<(double)(w[k]) )
+                        {
+                            k = j;
+                        }
+                    }
+                    v = w[i];
+                    w[i] = w[k];
+                    w[k] = v;
+                    for(j=1; j<=n; j++)
+                    {
+                        v = z2[j,i];
+                        z2[j,i] = z2[j,k];
+                        z2[j,k] = v;
+                    }
+                }
+                
+                //
+                // Transform Z2 and overwrite Z
+                //
+                z3 = new double[m+1, n+1];
+                for(i=1; i<=m; i++)
+                {
+                    for(i_=1; i_<=n;i_++)
+                    {
+                        z3[i,i_] = z2[i_,i];
+                    }
+                }
+                for(i=1; i<=n; i++)
+                {
+                    for(j=1; j<=m; j++)
+                    {
+                        i1_ = (1)-(0);
+                        v = 0.0;
+                        for(i_=0; i_<=n-1;i_++)
+                        {
+                            v += z[i-1,i_]*z3[j,i_+i1_];
+                        }
+                        z2[i,j] = v;
+                    }
+                }
+                z = new double[n-1+1, m-1+1];
+                for(i=1; i<=m; i++)
+                {
+                    i1_ = (1) - (0);
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        z[i_,i-1] = z2[i_+i1_,i];
+                    }
+                }
+                
+                //
+                // Store W
+                //
+                d = new double[m-1+1];
+                for(i=1; i<=m; i++)
+                {
+                    d[i-1] = w[i];
+                }
+                return result;
+            }
+            
+            //
+            // Eigen vectors are stored in Z
+            //
+            if( zneeded==2 )
+            {
+                
+                //
+                // Find eigen pairs
+                //
+                result = internalbisectioneigenvalues(d1, e1, n, 2, 2, a, b, 0, 0, -1, ref w, ref m, ref nsplit, ref iblock, ref isplit, ref errorcode);
+                if( !result || m==0 )
+                {
+                    m = 0;
+                    return result;
+                }
+                internaldstein(n, d1, e1, m, w, iblock, isplit, ref z2, ref ifail, ref cr);
+                if( cr!=0 )
+                {
+                    m = 0;
+                    result = false;
+                    return result;
+                }
+                
+                //
+                // Sort eigen values and vectors
+                //
+                for(i=1; i<=m; i++)
+                {
+                    k = i;
+                    for(j=i; j<=m; j++)
+                    {
+                        if( (double)(w[j])<(double)(w[k]) )
+                        {
+                            k = j;
+                        }
+                    }
+                    v = w[i];
+                    w[i] = w[k];
+                    w[k] = v;
+                    for(j=1; j<=n; j++)
+                    {
+                        v = z2[j,i];
+                        z2[j,i] = z2[j,k];
+                        z2[j,k] = v;
+                    }
+                }
+                
+                //
+                // Store W
+                //
+                d = new double[m-1+1];
+                for(i=1; i<=m; i++)
+                {
+                    d[i-1] = w[i];
+                }
+                z = new double[n-1+1, m-1+1];
+                for(i=1; i<=m; i++)
+                {
+                    i1_ = (1) - (0);
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        z[i_,i-1] = z2[i_+i1_,i];
+                    }
+                }
+                return result;
+            }
+            result = false;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Subroutine for finding tridiagonal matrix eigenvalues/vectors with given
+        indexes (in ascending order) by using the bisection and inverse iteraion.
+
+        Input parameters:
+            D       -   the main diagonal of a tridiagonal matrix.
+                        Array whose index ranges within [0..N-1].
+            E       -   the secondary diagonal of a tridiagonal matrix.
+                        Array whose index ranges within [0..N-2].
+            N       -   size of matrix. N>=0.
+            ZNeeded -   flag controlling whether the eigenvectors are needed or not.
+                        If ZNeeded is equal to:
+                         * 0, the eigenvectors are not needed;
+                         * 1, the eigenvectors of a tridiagonal matrix are multiplied
+                           by the square matrix Z. It is used if the
+                           tridiagonal matrix is obtained by the similarity transformation
+                           of a symmetric matrix.
+                         * 2, the eigenvectors of a tridiagonal matrix replace
+                           matrix Z.
+            I1, I2  -   index interval for searching (from I1 to I2).
+                        0 <= I1 <= I2 <= N-1.
+            Z       -   if ZNeeded is equal to:
+                         * 0, Z isn't used and remains unchanged;
+                         * 1, Z contains the square matrix (array whose indexes range within [0..N-1, 0..N-1])
+                           which reduces the given symmetric matrix to  tridiagonal form;
+                         * 2, Z isn't used (but changed on the exit).
+
+        Output parameters:
+            D       -   array of the eigenvalues found.
+                        Array whose index ranges within [0..I2-I1].
+            Z       -   if ZNeeded is equal to:
+                         * 0, doesn't contain any information;
+                         * 1, contains the product of a given NxN matrix Z (from the left) and
+                           Nx(I2-I1) matrix of the eigenvectors found (from the right).
+                           Array whose indexes range within [0..N-1, 0..I2-I1].
+                         * 2, contains the matrix of the eigenvalues found.
+                           Array whose indexes range within [0..N-1, 0..I2-I1].
+
+
+        Result:
+
+            True, if successful. In that case, D contains the eigenvalues,
+            Z contains the eigenvectors (if needed).
+            It should be noted that the subroutine changes the size of arrays D and Z.
+
+            False, if the bisection method subroutine wasn't able to find the eigenvalues
+            in the given interval or if the inverse iteration subroutine wasn't able
+            to find all the corresponding eigenvectors. In that case, the eigenvalues
+            and eigenvectors are not returned.
+
+          -- ALGLIB --
+             Copyright 25.12.2005 by Bochkanov Sergey
+        *************************************************************************/
+        public static bool smatrixtdevdi(ref double[] d,
+            double[] e,
+            int n,
+            int zneeded,
+            int i1,
+            int i2,
+            ref double[,] z)
+        {
+            bool result = new bool();
+            int errorcode = 0;
+            int nsplit = 0;
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int m = 0;
+            int cr = 0;
+            int[] iblock = new int[0];
+            int[] isplit = new int[0];
+            int[] ifail = new int[0];
+            double[] w = new double[0];
+            double[] d1 = new double[0];
+            double[] e1 = new double[0];
+            double[,] z2 = new double[0,0];
+            double[,] z3 = new double[0,0];
+            double v = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            alglib.ap.assert((0<=i1 && i1<=i2) && i2<n, "SMatrixTDEVDI: incorrect I1/I2!");
+            
+            //
+            // Copy D,E to D1, E1
+            //
+            d1 = new double[n+1];
+            i1_ = (0) - (1);
+            for(i_=1; i_<=n;i_++)
+            {
+                d1[i_] = d[i_+i1_];
+            }
+            if( n>1 )
+            {
+                e1 = new double[n-1+1];
+                i1_ = (0) - (1);
+                for(i_=1; i_<=n-1;i_++)
+                {
+                    e1[i_] = e[i_+i1_];
+                }
+            }
+            
+            //
+            // No eigen vectors
+            //
+            if( zneeded==0 )
+            {
+                result = internalbisectioneigenvalues(d1, e1, n, 3, 1, 0, 0, i1+1, i2+1, -1, ref w, ref m, ref nsplit, ref iblock, ref isplit, ref errorcode);
+                if( !result )
+                {
+                    return result;
+                }
+                if( m!=i2-i1+1 )
+                {
+                    result = false;
+                    return result;
+                }
+                d = new double[m-1+1];
+                for(i=1; i<=m; i++)
+                {
+                    d[i-1] = w[i];
+                }
+                return result;
+            }
+            
+            //
+            // Eigen vectors are multiplied by Z
+            //
+            if( zneeded==1 )
+            {
+                
+                //
+                // Find eigen pairs
+                //
+                result = internalbisectioneigenvalues(d1, e1, n, 3, 2, 0, 0, i1+1, i2+1, -1, ref w, ref m, ref nsplit, ref iblock, ref isplit, ref errorcode);
+                if( !result )
+                {
+                    return result;
+                }
+                if( m!=i2-i1+1 )
+                {
+                    result = false;
+                    return result;
+                }
+                internaldstein(n, d1, e1, m, w, iblock, isplit, ref z2, ref ifail, ref cr);
+                if( cr!=0 )
+                {
+                    result = false;
+                    return result;
+                }
+                
+                //
+                // Sort eigen values and vectors
+                //
+                for(i=1; i<=m; i++)
+                {
+                    k = i;
+                    for(j=i; j<=m; j++)
+                    {
+                        if( (double)(w[j])<(double)(w[k]) )
+                        {
+                            k = j;
+                        }
+                    }
+                    v = w[i];
+                    w[i] = w[k];
+                    w[k] = v;
+                    for(j=1; j<=n; j++)
+                    {
+                        v = z2[j,i];
+                        z2[j,i] = z2[j,k];
+                        z2[j,k] = v;
+                    }
+                }
+                
+                //
+                // Transform Z2 and overwrite Z
+                //
+                z3 = new double[m+1, n+1];
+                for(i=1; i<=m; i++)
+                {
+                    for(i_=1; i_<=n;i_++)
+                    {
+                        z3[i,i_] = z2[i_,i];
+                    }
+                }
+                for(i=1; i<=n; i++)
+                {
+                    for(j=1; j<=m; j++)
+                    {
+                        i1_ = (1)-(0);
+                        v = 0.0;
+                        for(i_=0; i_<=n-1;i_++)
+                        {
+                            v += z[i-1,i_]*z3[j,i_+i1_];
+                        }
+                        z2[i,j] = v;
+                    }
+                }
+                z = new double[n-1+1, m-1+1];
+                for(i=1; i<=m; i++)
+                {
+                    i1_ = (1) - (0);
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        z[i_,i-1] = z2[i_+i1_,i];
+                    }
+                }
+                
+                //
+                // Store W
+                //
+                d = new double[m-1+1];
+                for(i=1; i<=m; i++)
+                {
+                    d[i-1] = w[i];
+                }
+                return result;
+            }
+            
+            //
+            // Eigen vectors are stored in Z
+            //
+            if( zneeded==2 )
+            {
+                
+                //
+                // Find eigen pairs
+                //
+                result = internalbisectioneigenvalues(d1, e1, n, 3, 2, 0, 0, i1+1, i2+1, -1, ref w, ref m, ref nsplit, ref iblock, ref isplit, ref errorcode);
+                if( !result )
+                {
+                    return result;
+                }
+                if( m!=i2-i1+1 )
+                {
+                    result = false;
+                    return result;
+                }
+                internaldstein(n, d1, e1, m, w, iblock, isplit, ref z2, ref ifail, ref cr);
+                if( cr!=0 )
+                {
+                    result = false;
+                    return result;
+                }
+                
+                //
+                // Sort eigen values and vectors
+                //
+                for(i=1; i<=m; i++)
+                {
+                    k = i;
+                    for(j=i; j<=m; j++)
+                    {
+                        if( (double)(w[j])<(double)(w[k]) )
+                        {
+                            k = j;
+                        }
+                    }
+                    v = w[i];
+                    w[i] = w[k];
+                    w[k] = v;
+                    for(j=1; j<=n; j++)
+                    {
+                        v = z2[j,i];
+                        z2[j,i] = z2[j,k];
+                        z2[j,k] = v;
+                    }
+                }
+                
+                //
+                // Store Z
+                //
+                z = new double[n-1+1, m-1+1];
+                for(i=1; i<=m; i++)
+                {
+                    i1_ = (1) - (0);
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        z[i_,i-1] = z2[i_+i1_,i];
+                    }
+                }
+                
+                //
+                // Store W
+                //
+                d = new double[m-1+1];
+                for(i=1; i<=m; i++)
+                {
+                    d[i-1] = w[i];
+                }
+                return result;
+            }
+            result = false;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Finding eigenvalues and eigenvectors of a general matrix
+
+        The algorithm finds eigenvalues and eigenvectors of a general matrix by
+        using the QR algorithm with multiple shifts. The algorithm can find
+        eigenvalues and both left and right eigenvectors.
+
+        The right eigenvector is a vector x such that A*x = w*x, and the left
+        eigenvector is a vector y such that y'*A = w*y' (here y' implies a complex
+        conjugate transposition of vector y).
+
+        Input parameters:
+            A       -   matrix. Array whose indexes range within [0..N-1, 0..N-1].
+            N       -   size of matrix A.
+            VNeeded -   flag controlling whether eigenvectors are needed or not.
+                        If VNeeded is equal to:
+                         * 0, eigenvectors are not returned;
+                         * 1, right eigenvectors are returned;
+                         * 2, left eigenvectors are returned;
+                         * 3, both left and right eigenvectors are returned.
+
+        Output parameters:
+            WR      -   real parts of eigenvalues.
+                        Array whose index ranges within [0..N-1].
+            WR      -   imaginary parts of eigenvalues.
+                        Array whose index ranges within [0..N-1].
+            VL, VR  -   arrays of left and right eigenvectors (if they are needed).
+                        If WI[i]=0, the respective eigenvalue is a real number,
+                        and it corresponds to the column number I of matrices VL/VR.
+                        If WI[i]>0, we have a pair of complex conjugate numbers with
+                        positive and negative imaginary parts:
+                            the first eigenvalue WR[i] + sqrt(-1)*WI[i];
+                            the second eigenvalue WR[i+1] + sqrt(-1)*WI[i+1];
+                            WI[i]>0
+                            WI[i+1] = -WI[i] < 0
+                        In that case, the eigenvector  corresponding to the first
+                        eigenvalue is located in i and i+1 columns of matrices
+                        VL/VR (the column number i contains the real part, and the
+                        column number i+1 contains the imaginary part), and the vector
+                        corresponding to the second eigenvalue is a complex conjugate to
+                        the first vector.
+                        Arrays whose indexes range within [0..N-1, 0..N-1].
+
+        Result:
+            True, if the algorithm has converged.
+            False, if the algorithm has not converged.
+
+        Note 1:
+            Some users may ask the following question: what if WI[N-1]>0?
+            WI[N] must contain an eigenvalue which is complex conjugate to the
+            N-th eigenvalue, but the array has only size N?
+            The answer is as follows: such a situation cannot occur because the
+            algorithm finds a pairs of eigenvalues, therefore, if WI[i]>0, I is
+            strictly less than N-1.
+
+        Note 2:
+            The algorithm performance depends on the value of the internal parameter
+            NS of the InternalSchurDecomposition subroutine which defines the number
+            of shifts in the QR algorithm (similarly to the block width in block-matrix
+            algorithms of linear algebra). If you require maximum performance
+            on your machine, it is recommended to adjust this parameter manually.
+
+
+        See also the InternalTREVC subroutine.
+
+        The algorithm is based on the LAPACK 3.0 library.
+        *************************************************************************/
+        public static bool rmatrixevd(double[,] a,
+            int n,
+            int vneeded,
+            ref double[] wr,
+            ref double[] wi,
+            ref double[,] vl,
+            ref double[,] vr)
+        {
+            bool result = new bool();
+            double[,] a1 = new double[0,0];
+            double[,] vl1 = new double[0,0];
+            double[,] vr1 = new double[0,0];
+            double[] wr1 = new double[0];
+            double[] wi1 = new double[0];
+            int i = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            a = (double[,])a.Clone();
+            wr = new double[0];
+            wi = new double[0];
+            vl = new double[0,0];
+            vr = new double[0,0];
+
+            alglib.ap.assert(vneeded>=0 && vneeded<=3, "RMatrixEVD: incorrect VNeeded!");
+            a1 = new double[n+1, n+1];
+            for(i=1; i<=n; i++)
+            {
+                i1_ = (0) - (1);
+                for(i_=1; i_<=n;i_++)
+                {
+                    a1[i,i_] = a[i-1,i_+i1_];
+                }
+            }
+            result = nonsymmetricevd(a1, n, vneeded, ref wr1, ref wi1, ref vl1, ref vr1);
+            if( result )
+            {
+                wr = new double[n-1+1];
+                wi = new double[n-1+1];
+                i1_ = (1) - (0);
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    wr[i_] = wr1[i_+i1_];
+                }
+                i1_ = (1) - (0);
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    wi[i_] = wi1[i_+i1_];
+                }
+                if( vneeded==2 || vneeded==3 )
+                {
+                    vl = new double[n-1+1, n-1+1];
+                    for(i=0; i<=n-1; i++)
+                    {
+                        i1_ = (1) - (0);
+                        for(i_=0; i_<=n-1;i_++)
+                        {
+                            vl[i,i_] = vl1[i+1,i_+i1_];
+                        }
+                    }
+                }
+                if( vneeded==1 || vneeded==3 )
+                {
+                    vr = new double[n-1+1, n-1+1];
+                    for(i=0; i<=n-1; i++)
+                    {
+                        i1_ = (1) - (0);
+                        for(i_=0; i_<=n-1;i_++)
+                        {
+                            vr[i,i_] = vr1[i+1,i_+i1_];
+                        }
+                    }
+                }
+            }
+            return result;
+        }
+
+
+        private static bool tridiagonalevd(ref double[] d,
+            double[] e,
+            int n,
+            int zneeded,
+            ref double[,] z)
+        {
+            bool result = new bool();
+            int maxit = 0;
+            int i = 0;
+            int ii = 0;
+            int iscale = 0;
+            int j = 0;
+            int jtot = 0;
+            int k = 0;
+            int t = 0;
+            int l = 0;
+            int l1 = 0;
+            int lend = 0;
+            int lendm1 = 0;
+            int lendp1 = 0;
+            int lendsv = 0;
+            int lm1 = 0;
+            int lsv = 0;
+            int m = 0;
+            int mm = 0;
+            int mm1 = 0;
+            int nm1 = 0;
+            int nmaxit = 0;
+            int tmpint = 0;
+            double anorm = 0;
+            double b = 0;
+            double c = 0;
+            double eps = 0;
+            double eps2 = 0;
+            double f = 0;
+            double g = 0;
+            double p = 0;
+            double r = 0;
+            double rt1 = 0;
+            double rt2 = 0;
+            double s = 0;
+            double safmax = 0;
+            double safmin = 0;
+            double ssfmax = 0;
+            double ssfmin = 0;
+            double tst = 0;
+            double tmp = 0;
+            double[] work1 = new double[0];
+            double[] work2 = new double[0];
+            double[] workc = new double[0];
+            double[] works = new double[0];
+            double[] wtemp = new double[0];
+            bool gotoflag = new bool();
+            int zrows = 0;
+            bool wastranspose = new bool();
+            int i_ = 0;
+
+            e = (double[])e.Clone();
+
+            alglib.ap.assert(zneeded>=0 && zneeded<=3, "TridiagonalEVD: Incorrent ZNeeded");
+            
+            //
+            // Quick return if possible
+            //
+            if( zneeded<0 || zneeded>3 )
+            {
+                result = false;
+                return result;
+            }
+            result = true;
+            if( n==0 )
+            {
+                return result;
+            }
+            if( n==1 )
+            {
+                if( zneeded==2 || zneeded==3 )
+                {
+                    z = new double[1+1, 1+1];
+                    z[1,1] = 1;
+                }
+                return result;
+            }
+            maxit = 30;
+            
+            //
+            // Initialize arrays
+            //
+            wtemp = new double[n+1];
+            work1 = new double[n-1+1];
+            work2 = new double[n-1+1];
+            workc = new double[n+1];
+            works = new double[n+1];
+            
+            //
+            // Determine the unit roundoff and over/underflow thresholds.
+            //
+            eps = math.machineepsilon;
+            eps2 = math.sqr(eps);
+            safmin = math.minrealnumber;
+            safmax = math.maxrealnumber;
+            ssfmax = Math.Sqrt(safmax)/3;
+            ssfmin = Math.Sqrt(safmin)/eps2;
+            
+            //
+            // Prepare Z
+            //
+            // Here we are using transposition to get rid of column operations
+            //
+            //
+            wastranspose = false;
+            zrows = 0;
+            if( zneeded==1 )
+            {
+                zrows = n;
+            }
+            if( zneeded==2 )
+            {
+                zrows = n;
+            }
+            if( zneeded==3 )
+            {
+                zrows = 1;
+            }
+            if( zneeded==1 )
+            {
+                wastranspose = true;
+                blas.inplacetranspose(ref z, 1, n, 1, n, ref wtemp);
+            }
+            if( zneeded==2 )
+            {
+                wastranspose = true;
+                z = new double[n+1, n+1];
+                for(i=1; i<=n; i++)
+                {
+                    for(j=1; j<=n; j++)
+                    {
+                        if( i==j )
+                        {
+                            z[i,j] = 1;
+                        }
+                        else
+                        {
+                            z[i,j] = 0;
+                        }
+                    }
+                }
+            }
+            if( zneeded==3 )
+            {
+                wastranspose = false;
+                z = new double[1+1, n+1];
+                for(j=1; j<=n; j++)
+                {
+                    if( j==1 )
+                    {
+                        z[1,j] = 1;
+                    }
+                    else
+                    {
+                        z[1,j] = 0;
+                    }
+                }
+            }
+            nmaxit = n*maxit;
+            jtot = 0;
+            
+            //
+            // Determine where the matrix splits and choose QL or QR iteration
+            // for each block, according to whether top or bottom diagonal
+            // element is smaller.
+            //
+            l1 = 1;
+            nm1 = n-1;
+            while( true )
+            {
+                if( l1>n )
+                {
+                    break;
+                }
+                if( l1>1 )
+                {
+                    e[l1-1] = 0;
+                }
+                gotoflag = false;
+                m = l1;
+                if( l1<=nm1 )
+                {
+                    for(m=l1; m<=nm1; m++)
+                    {
+                        tst = Math.Abs(e[m]);
+                        if( (double)(tst)==(double)(0) )
+                        {
+                            gotoflag = true;
+                            break;
+                        }
+                        if( (double)(tst)<=(double)(Math.Sqrt(Math.Abs(d[m]))*Math.Sqrt(Math.Abs(d[m+1]))*eps) )
+                        {
+                            e[m] = 0;
+                            gotoflag = true;
+                            break;
+                        }
+                    }
+                }
+                if( !gotoflag )
+                {
+                    m = n;
+                }
+                
+                //
+                // label 30:
+                //
+                l = l1;
+                lsv = l;
+                lend = m;
+                lendsv = lend;
+                l1 = m+1;
+                if( lend==l )
+                {
+                    continue;
+                }
+                
+                //
+                // Scale submatrix in rows and columns L to LEND
+                //
+                if( l==lend )
+                {
+                    anorm = Math.Abs(d[l]);
+                }
+                else
+                {
+                    anorm = Math.Max(Math.Abs(d[l])+Math.Abs(e[l]), Math.Abs(e[lend-1])+Math.Abs(d[lend]));
+                    for(i=l+1; i<=lend-1; i++)
+                    {
+                        anorm = Math.Max(anorm, Math.Abs(d[i])+Math.Abs(e[i])+Math.Abs(e[i-1]));
+                    }
+                }
+                iscale = 0;
+                if( (double)(anorm)==(double)(0) )
+                {
+                    continue;
+                }
+                if( (double)(anorm)>(double)(ssfmax) )
+                {
+                    iscale = 1;
+                    tmp = ssfmax/anorm;
+                    tmpint = lend-1;
+                    for(i_=l; i_<=lend;i_++)
+                    {
+                        d[i_] = tmp*d[i_];
+                    }
+                    for(i_=l; i_<=tmpint;i_++)
+                    {
+                        e[i_] = tmp*e[i_];
+                    }
+                }
+                if( (double)(anorm)<(double)(ssfmin) )
+                {
+                    iscale = 2;
+                    tmp = ssfmin/anorm;
+                    tmpint = lend-1;
+                    for(i_=l; i_<=lend;i_++)
+                    {
+                        d[i_] = tmp*d[i_];
+                    }
+                    for(i_=l; i_<=tmpint;i_++)
+                    {
+                        e[i_] = tmp*e[i_];
+                    }
+                }
+                
+                //
+                // Choose between QL and QR iteration
+                //
+                if( (double)(Math.Abs(d[lend]))<(double)(Math.Abs(d[l])) )
+                {
+                    lend = lsv;
+                    l = lendsv;
+                }
+                if( lend>l )
+                {
+                    
+                    //
+                    // QL Iteration
+                    //
+                    // Look for small subdiagonal element.
+                    //
+                    while( true )
+                    {
+                        gotoflag = false;
+                        if( l!=lend )
+                        {
+                            lendm1 = lend-1;
+                            for(m=l; m<=lendm1; m++)
+                            {
+                                tst = math.sqr(Math.Abs(e[m]));
+                                if( (double)(tst)<=(double)(eps2*Math.Abs(d[m])*Math.Abs(d[m+1])+safmin) )
+                                {
+                                    gotoflag = true;
+                                    break;
+                                }
+                            }
+                        }
+                        if( !gotoflag )
+                        {
+                            m = lend;
+                        }
+                        if( m<lend )
+                        {
+                            e[m] = 0;
+                        }
+                        p = d[l];
+                        if( m!=l )
+                        {
+                            
+                            //
+                            // If remaining matrix is 2-by-2, use DLAE2 or SLAEV2
+                            // to compute its eigensystem.
+                            //
+                            if( m==l+1 )
+                            {
+                                if( zneeded>0 )
+                                {
+                                    tdevdev2(d[l], e[l], d[l+1], ref rt1, ref rt2, ref c, ref s);
+                                    work1[l] = c;
+                                    work2[l] = s;
+                                    workc[1] = work1[l];
+                                    works[1] = work2[l];
+                                    if( !wastranspose )
+                                    {
+                                        rotations.applyrotationsfromtheright(false, 1, zrows, l, l+1, workc, works, ref z, ref wtemp);
+                                    }
+                                    else
+                                    {
+                                        rotations.applyrotationsfromtheleft(false, l, l+1, 1, zrows, workc, works, ref z, ref wtemp);
+                                    }
+                                }
+                                else
+                                {
+                                    tdevde2(d[l], e[l], d[l+1], ref rt1, ref rt2);
+                                }
+                                d[l] = rt1;
+                                d[l+1] = rt2;
+                                e[l] = 0;
+                                l = l+2;
+                                if( l<=lend )
+                                {
+                                    continue;
+                                }
+                                
+                                //
+                                // GOTO 140
+                                //
+                                break;
+                            }
+                            if( jtot==nmaxit )
+                            {
+                                
+                                //
+                                // GOTO 140
+                                //
+                                break;
+                            }
+                            jtot = jtot+1;
+                            
+                            //
+                            // Form shift.
+                            //
+                            g = (d[l+1]-p)/(2*e[l]);
+                            r = tdevdpythag(g, 1);
+                            g = d[m]-p+e[l]/(g+tdevdextsign(r, g));
+                            s = 1;
+                            c = 1;
+                            p = 0;
+                            
+                            //
+                            // Inner loop
+                            //
+                            mm1 = m-1;
+                            for(i=mm1; i>=l; i--)
+                            {
+                                f = s*e[i];
+                                b = c*e[i];
+                                rotations.generaterotation(g, f, ref c, ref s, ref r);
+                                if( i!=m-1 )
+                                {
+                                    e[i+1] = r;
+                                }
+                                g = d[i+1]-p;
+                                r = (d[i]-g)*s+2*c*b;
+                                p = s*r;
+                                d[i+1] = g+p;
+                                g = c*r-b;
+                                
+                                //
+                                // If eigenvectors are desired, then save rotations.
+                                //
+                                if( zneeded>0 )
+                                {
+                                    work1[i] = c;
+                                    work2[i] = -s;
+                                }
+                            }
+                            
+                            //
+                            // If eigenvectors are desired, then apply saved rotations.
+                            //
+                            if( zneeded>0 )
+                            {
+                                for(i=l; i<=m-1; i++)
+                                {
+                                    workc[i-l+1] = work1[i];
+                                    works[i-l+1] = work2[i];
+                                }
+                                if( !wastranspose )
+                                {
+                                    rotations.applyrotationsfromtheright(false, 1, zrows, l, m, workc, works, ref z, ref wtemp);
+                                }
+                                else
+                                {
+                                    rotations.applyrotationsfromtheleft(false, l, m, 1, zrows, workc, works, ref z, ref wtemp);
+                                }
+                            }
+                            d[l] = d[l]-p;
+                            e[l] = g;
+                            continue;
+                        }
+                        
+                        //
+                        // Eigenvalue found.
+                        //
+                        d[l] = p;
+                        l = l+1;
+                        if( l<=lend )
+                        {
+                            continue;
+                        }
+                        break;
+                    }
+                }
+                else
+                {
+                    
+                    //
+                    // QR Iteration
+                    //
+                    // Look for small superdiagonal element.
+                    //
+                    while( true )
+                    {
+                        gotoflag = false;
+                        if( l!=lend )
+                        {
+                            lendp1 = lend+1;
+                            for(m=l; m>=lendp1; m--)
+                            {
+                                tst = math.sqr(Math.Abs(e[m-1]));
+                                if( (double)(tst)<=(double)(eps2*Math.Abs(d[m])*Math.Abs(d[m-1])+safmin) )
+                                {
+                                    gotoflag = true;
+                                    break;
+                                }
+                            }
+                        }
+                        if( !gotoflag )
+                        {
+                            m = lend;
+                        }
+                        if( m>lend )
+                        {
+                            e[m-1] = 0;
+                        }
+                        p = d[l];
+                        if( m!=l )
+                        {
+                            
+                            //
+                            // If remaining matrix is 2-by-2, use DLAE2 or SLAEV2
+                            // to compute its eigensystem.
+                            //
+                            if( m==l-1 )
+                            {
+                                if( zneeded>0 )
+                                {
+                                    tdevdev2(d[l-1], e[l-1], d[l], ref rt1, ref rt2, ref c, ref s);
+                                    work1[m] = c;
+                                    work2[m] = s;
+                                    workc[1] = c;
+                                    works[1] = s;
+                                    if( !wastranspose )
+                                    {
+                                        rotations.applyrotationsfromtheright(true, 1, zrows, l-1, l, workc, works, ref z, ref wtemp);
+                                    }
+                                    else
+                                    {
+                                        rotations.applyrotationsfromtheleft(true, l-1, l, 1, zrows, workc, works, ref z, ref wtemp);
+                                    }
+                                }
+                                else
+                                {
+                                    tdevde2(d[l-1], e[l-1], d[l], ref rt1, ref rt2);
+                                }
+                                d[l-1] = rt1;
+                                d[l] = rt2;
+                                e[l-1] = 0;
+                                l = l-2;
+                                if( l>=lend )
+                                {
+                                    continue;
+                                }
+                                break;
+                            }
+                            if( jtot==nmaxit )
+                            {
+                                break;
+                            }
+                            jtot = jtot+1;
+                            
+                            //
+                            // Form shift.
+                            //
+                            g = (d[l-1]-p)/(2*e[l-1]);
+                            r = tdevdpythag(g, 1);
+                            g = d[m]-p+e[l-1]/(g+tdevdextsign(r, g));
+                            s = 1;
+                            c = 1;
+                            p = 0;
+                            
+                            //
+                            // Inner loop
+                            //
+                            lm1 = l-1;
+                            for(i=m; i<=lm1; i++)
+                            {
+                                f = s*e[i];
+                                b = c*e[i];
+                                rotations.generaterotation(g, f, ref c, ref s, ref r);
+                                if( i!=m )
+                                {
+                                    e[i-1] = r;
+                                }
+                                g = d[i]-p;
+                                r = (d[i+1]-g)*s+2*c*b;
+                                p = s*r;
+                                d[i] = g+p;
+                                g = c*r-b;
+                                
+                                //
+                                // If eigenvectors are desired, then save rotations.
+                                //
+                                if( zneeded>0 )
+                                {
+                                    work1[i] = c;
+                                    work2[i] = s;
+                                }
+                            }
+                            
+                            //
+                            // If eigenvectors are desired, then apply saved rotations.
+                            //
+                            if( zneeded>0 )
+                            {
+                                mm = l-m+1;
+                                for(i=m; i<=l-1; i++)
+                                {
+                                    workc[i-m+1] = work1[i];
+                                    works[i-m+1] = work2[i];
+                                }
+                                if( !wastranspose )
+                                {
+                                    rotations.applyrotationsfromtheright(true, 1, zrows, m, l, workc, works, ref z, ref wtemp);
+                                }
+                                else
+                                {
+                                    rotations.applyrotationsfromtheleft(true, m, l, 1, zrows, workc, works, ref z, ref wtemp);
+                                }
+                            }
+                            d[l] = d[l]-p;
+                            e[lm1] = g;
+                            continue;
+                        }
+                        
+                        //
+                        // Eigenvalue found.
+                        //
+                        d[l] = p;
+                        l = l-1;
+                        if( l>=lend )
+                        {
+                            continue;
+                        }
+                        break;
+                    }
+                }
+                
+                //
+                // Undo scaling if necessary
+                //
+                if( iscale==1 )
+                {
+                    tmp = anorm/ssfmax;
+                    tmpint = lendsv-1;
+                    for(i_=lsv; i_<=lendsv;i_++)
+                    {
+                        d[i_] = tmp*d[i_];
+                    }
+                    for(i_=lsv; i_<=tmpint;i_++)
+                    {
+                        e[i_] = tmp*e[i_];
+                    }
+                }
+                if( iscale==2 )
+                {
+                    tmp = anorm/ssfmin;
+                    tmpint = lendsv-1;
+                    for(i_=lsv; i_<=lendsv;i_++)
+                    {
+                        d[i_] = tmp*d[i_];
+                    }
+                    for(i_=lsv; i_<=tmpint;i_++)
+                    {
+                        e[i_] = tmp*e[i_];
+                    }
+                }
+                
+                //
+                // Check for no convergence to an eigenvalue after a total
+                // of N*MAXIT iterations.
+                //
+                if( jtot>=nmaxit )
+                {
+                    result = false;
+                    if( wastranspose )
+                    {
+                        blas.inplacetranspose(ref z, 1, n, 1, n, ref wtemp);
+                    }
+                    return result;
+                }
+            }
+            
+            //
+            // Order eigenvalues and eigenvectors.
+            //
+            if( zneeded==0 )
+            {
+                
+                //
+                // Sort
+                //
+                if( n==1 )
+                {
+                    return result;
+                }
+                if( n==2 )
+                {
+                    if( (double)(d[1])>(double)(d[2]) )
+                    {
+                        tmp = d[1];
+                        d[1] = d[2];
+                        d[2] = tmp;
+                    }
+                    return result;
+                }
+                i = 2;
+                do
+                {
+                    t = i;
+                    while( t!=1 )
+                    {
+                        k = t/2;
+                        if( (double)(d[k])>=(double)(d[t]) )
+                        {
+                            t = 1;
+                        }
+                        else
+                        {
+                            tmp = d[k];
+                            d[k] = d[t];
+                            d[t] = tmp;
+                            t = k;
+                        }
+                    }
+                    i = i+1;
+                }
+                while( i<=n );
+                i = n-1;
+                do
+                {
+                    tmp = d[i+1];
+                    d[i+1] = d[1];
+                    d[1] = tmp;
+                    t = 1;
+                    while( t!=0 )
+                    {
+                        k = 2*t;
+                        if( k>i )
+                        {
+                            t = 0;
+                        }
+                        else
+                        {
+                            if( k<i )
+                            {
+                                if( (double)(d[k+1])>(double)(d[k]) )
+                                {
+                                    k = k+1;
+                                }
+                            }
+                            if( (double)(d[t])>=(double)(d[k]) )
+                            {
+                                t = 0;
+                            }
+                            else
+                            {
+                                tmp = d[k];
+                                d[k] = d[t];
+                                d[t] = tmp;
+                                t = k;
+                            }
+                        }
+                    }
+                    i = i-1;
+                }
+                while( i>=1 );
+            }
+            else
+            {
+                
+                //
+                // Use Selection Sort to minimize swaps of eigenvectors
+                //
+                for(ii=2; ii<=n; ii++)
+                {
+                    i = ii-1;
+                    k = i;
+                    p = d[i];
+                    for(j=ii; j<=n; j++)
+                    {
+                        if( (double)(d[j])<(double)(p) )
+                        {
+                            k = j;
+                            p = d[j];
+                        }
+                    }
+                    if( k!=i )
+                    {
+                        d[k] = d[i];
+                        d[i] = p;
+                        if( wastranspose )
+                        {
+                            for(i_=1; i_<=n;i_++)
+                            {
+                                wtemp[i_] = z[i,i_];
+                            }
+                            for(i_=1; i_<=n;i_++)
+                            {
+                                z[i,i_] = z[k,i_];
+                            }
+                            for(i_=1; i_<=n;i_++)
+                            {
+                                z[k,i_] = wtemp[i_];
+                            }
+                        }
+                        else
+                        {
+                            for(i_=1; i_<=zrows;i_++)
+                            {
+                                wtemp[i_] = z[i_,i];
+                            }
+                            for(i_=1; i_<=zrows;i_++)
+                            {
+                                z[i_,i] = z[i_,k];
+                            }
+                            for(i_=1; i_<=zrows;i_++)
+                            {
+                                z[i_,k] = wtemp[i_];
+                            }
+                        }
+                    }
+                }
+                if( wastranspose )
+                {
+                    blas.inplacetranspose(ref z, 1, n, 1, n, ref wtemp);
+                }
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        DLAE2  computes the eigenvalues of a 2-by-2 symmetric matrix
+           [  A   B  ]
+           [  B   C  ].
+        On return, RT1 is the eigenvalue of larger absolute value, and RT2
+        is the eigenvalue of smaller absolute value.
+
+          -- LAPACK auxiliary routine (version 3.0) --
+             Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd.,
+             Courant Institute, Argonne National Lab, and Rice University
+             October 31, 1992
+        *************************************************************************/
+        private static void tdevde2(double a,
+            double b,
+            double c,
+            ref double rt1,
+            ref double rt2)
+        {
+            double ab = 0;
+            double acmn = 0;
+            double acmx = 0;
+            double adf = 0;
+            double df = 0;
+            double rt = 0;
+            double sm = 0;
+            double tb = 0;
+
+            rt1 = 0;
+            rt2 = 0;
+
+            sm = a+c;
+            df = a-c;
+            adf = Math.Abs(df);
+            tb = b+b;
+            ab = Math.Abs(tb);
+            if( (double)(Math.Abs(a))>(double)(Math.Abs(c)) )
+            {
+                acmx = a;
+                acmn = c;
+            }
+            else
+            {
+                acmx = c;
+                acmn = a;
+            }
+            if( (double)(adf)>(double)(ab) )
+            {
+                rt = adf*Math.Sqrt(1+math.sqr(ab/adf));
+            }
+            else
+            {
+                if( (double)(adf)<(double)(ab) )
+                {
+                    rt = ab*Math.Sqrt(1+math.sqr(adf/ab));
+                }
+                else
+                {
+                    
+                    //
+                    // Includes case AB=ADF=0
+                    //
+                    rt = ab*Math.Sqrt(2);
+                }
+            }
+            if( (double)(sm)<(double)(0) )
+            {
+                rt1 = 0.5*(sm-rt);
+                
+                //
+                // Order of execution important.
+                // To get fully accurate smaller eigenvalue,
+                // next line needs to be executed in higher precision.
+                //
+                rt2 = acmx/rt1*acmn-b/rt1*b;
+            }
+            else
+            {
+                if( (double)(sm)>(double)(0) )
+                {
+                    rt1 = 0.5*(sm+rt);
+                    
+                    //
+                    // Order of execution important.
+                    // To get fully accurate smaller eigenvalue,
+                    // next line needs to be executed in higher precision.
+                    //
+                    rt2 = acmx/rt1*acmn-b/rt1*b;
+                }
+                else
+                {
+                    
+                    //
+                    // Includes case RT1 = RT2 = 0
+                    //
+                    rt1 = 0.5*rt;
+                    rt2 = -(0.5*rt);
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        DLAEV2 computes the eigendecomposition of a 2-by-2 symmetric matrix
+
+           [  A   B  ]
+           [  B   C  ].
+
+        On return, RT1 is the eigenvalue of larger absolute value, RT2 is the
+        eigenvalue of smaller absolute value, and (CS1,SN1) is the unit right
+        eigenvector for RT1, giving the decomposition
+
+           [ CS1  SN1 ] [  A   B  ] [ CS1 -SN1 ]  =  [ RT1  0  ]
+           [-SN1  CS1 ] [  B   C  ] [ SN1  CS1 ]     [  0  RT2 ].
+
+
+          -- LAPACK auxiliary routine (version 3.0) --
+             Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd.,
+             Courant Institute, Argonne National Lab, and Rice University
+             October 31, 1992
+        *************************************************************************/
+        private static void tdevdev2(double a,
+            double b,
+            double c,
+            ref double rt1,
+            ref double rt2,
+            ref double cs1,
+            ref double sn1)
+        {
+            int sgn1 = 0;
+            int sgn2 = 0;
+            double ab = 0;
+            double acmn = 0;
+            double acmx = 0;
+            double acs = 0;
+            double adf = 0;
+            double cs = 0;
+            double ct = 0;
+            double df = 0;
+            double rt = 0;
+            double sm = 0;
+            double tb = 0;
+            double tn = 0;
+
+            rt1 = 0;
+            rt2 = 0;
+            cs1 = 0;
+            sn1 = 0;
+
+            
+            //
+            // Compute the eigenvalues
+            //
+            sm = a+c;
+            df = a-c;
+            adf = Math.Abs(df);
+            tb = b+b;
+            ab = Math.Abs(tb);
+            if( (double)(Math.Abs(a))>(double)(Math.Abs(c)) )
+            {
+                acmx = a;
+                acmn = c;
+            }
+            else
+            {
+                acmx = c;
+                acmn = a;
+            }
+            if( (double)(adf)>(double)(ab) )
+            {
+                rt = adf*Math.Sqrt(1+math.sqr(ab/adf));
+            }
+            else
+            {
+                if( (double)(adf)<(double)(ab) )
+                {
+                    rt = ab*Math.Sqrt(1+math.sqr(adf/ab));
+                }
+                else
+                {
+                    
+                    //
+                    // Includes case AB=ADF=0
+                    //
+                    rt = ab*Math.Sqrt(2);
+                }
+            }
+            if( (double)(sm)<(double)(0) )
+            {
+                rt1 = 0.5*(sm-rt);
+                sgn1 = -1;
+                
+                //
+                // Order of execution important.
+                // To get fully accurate smaller eigenvalue,
+                // next line needs to be executed in higher precision.
+                //
+                rt2 = acmx/rt1*acmn-b/rt1*b;
+            }
+            else
+            {
+                if( (double)(sm)>(double)(0) )
+                {
+                    rt1 = 0.5*(sm+rt);
+                    sgn1 = 1;
+                    
+                    //
+                    // Order of execution important.
+                    // To get fully accurate smaller eigenvalue,
+                    // next line needs to be executed in higher precision.
+                    //
+                    rt2 = acmx/rt1*acmn-b/rt1*b;
+                }
+                else
+                {
+                    
+                    //
+                    // Includes case RT1 = RT2 = 0
+                    //
+                    rt1 = 0.5*rt;
+                    rt2 = -(0.5*rt);
+                    sgn1 = 1;
+                }
+            }
+            
+            //
+            // Compute the eigenvector
+            //
+            if( (double)(df)>=(double)(0) )
+            {
+                cs = df+rt;
+                sgn2 = 1;
+            }
+            else
+            {
+                cs = df-rt;
+                sgn2 = -1;
+            }
+            acs = Math.Abs(cs);
+            if( (double)(acs)>(double)(ab) )
+            {
+                ct = -(tb/cs);
+                sn1 = 1/Math.Sqrt(1+ct*ct);
+                cs1 = ct*sn1;
+            }
+            else
+            {
+                if( (double)(ab)==(double)(0) )
+                {
+                    cs1 = 1;
+                    sn1 = 0;
+                }
+                else
+                {
+                    tn = -(cs/tb);
+                    cs1 = 1/Math.Sqrt(1+tn*tn);
+                    sn1 = tn*cs1;
+                }
+            }
+            if( sgn1==sgn2 )
+            {
+                tn = cs1;
+                cs1 = -sn1;
+                sn1 = tn;
+            }
+        }
+
+
+        /*************************************************************************
+        Internal routine
+        *************************************************************************/
+        private static double tdevdpythag(double a,
+            double b)
+        {
+            double result = 0;
+
+            if( (double)(Math.Abs(a))<(double)(Math.Abs(b)) )
+            {
+                result = Math.Abs(b)*Math.Sqrt(1+math.sqr(a/b));
+            }
+            else
+            {
+                result = Math.Abs(a)*Math.Sqrt(1+math.sqr(b/a));
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        Internal routine
+        *************************************************************************/
+        private static double tdevdextsign(double a,
+            double b)
+        {
+            double result = 0;
+
+            if( (double)(b)>=(double)(0) )
+            {
+                result = Math.Abs(a);
+            }
+            else
+            {
+                result = -Math.Abs(a);
+            }
+            return result;
+        }
+
+
+        private static bool internalbisectioneigenvalues(double[] d,
+            double[] e,
+            int n,
+            int irange,
+            int iorder,
+            double vl,
+            double vu,
+            int il,
+            int iu,
+            double abstol,
+            ref double[] w,
+            ref int m,
+            ref int nsplit,
+            ref int[] iblock,
+            ref int[] isplit,
+            ref int errorcode)
+        {
+            bool result = new bool();
+            double fudge = 0;
+            double relfac = 0;
+            bool ncnvrg = new bool();
+            bool toofew = new bool();
+            int ib = 0;
+            int ibegin = 0;
+            int idiscl = 0;
+            int idiscu = 0;
+            int ie = 0;
+            int iend = 0;
+            int iinfo = 0;
+            int im = 0;
+            int iin = 0;
+            int ioff = 0;
+            int iout = 0;
+            int itmax = 0;
+            int iw = 0;
+            int iwoff = 0;
+            int j = 0;
+            int itmp1 = 0;
+            int jb = 0;
+            int jdisc = 0;
+            int je = 0;
+            int nwl = 0;
+            int nwu = 0;
+            double atoli = 0;
+            double bnorm = 0;
+            double gl = 0;
+            double gu = 0;
+            double pivmin = 0;
+            double rtoli = 0;
+            double safemn = 0;
+            double tmp1 = 0;
+            double tmp2 = 0;
+            double tnorm = 0;
+            double ulp = 0;
+            double wkill = 0;
+            double wl = 0;
+            double wlu = 0;
+            double wu = 0;
+            double wul = 0;
+            double scalefactor = 0;
+            double t = 0;
+            int[] idumma = new int[0];
+            double[] work = new double[0];
+            int[] iwork = new int[0];
+            int[] ia1s2 = new int[0];
+            double[] ra1s2 = new double[0];
+            double[,] ra1s2x2 = new double[0,0];
+            int[,] ia1s2x2 = new int[0,0];
+            double[] ra1siin = new double[0];
+            double[] ra2siin = new double[0];
+            double[] ra3siin = new double[0];
+            double[] ra4siin = new double[0];
+            double[,] ra1siinx2 = new double[0,0];
+            int[,] ia1siinx2 = new int[0,0];
+            int[] iworkspace = new int[0];
+            double[] rworkspace = new double[0];
+            int tmpi = 0;
+
+            d = (double[])d.Clone();
+            e = (double[])e.Clone();
+            w = new double[0];
+            m = 0;
+            nsplit = 0;
+            iblock = new int[0];
+            isplit = new int[0];
+            errorcode = 0;
+
+            
+            //
+            // Quick return if possible
+            //
+            m = 0;
+            if( n==0 )
+            {
+                result = true;
+                return result;
+            }
+            
+            //
+            // Get machine constants
+            // NB is the minimum vector length for vector bisection, or 0
+            // if only scalar is to be done.
+            //
+            fudge = 2;
+            relfac = 2;
+            safemn = math.minrealnumber;
+            ulp = 2*math.machineepsilon;
+            rtoli = ulp*relfac;
+            idumma = new int[1+1];
+            work = new double[4*n+1];
+            iwork = new int[3*n+1];
+            w = new double[n+1];
+            iblock = new int[n+1];
+            isplit = new int[n+1];
+            ia1s2 = new int[2+1];
+            ra1s2 = new double[2+1];
+            ra1s2x2 = new double[2+1, 2+1];
+            ia1s2x2 = new int[2+1, 2+1];
+            ra1siin = new double[n+1];
+            ra2siin = new double[n+1];
+            ra3siin = new double[n+1];
+            ra4siin = new double[n+1];
+            ra1siinx2 = new double[n+1, 2+1];
+            ia1siinx2 = new int[n+1, 2+1];
+            iworkspace = new int[n+1];
+            rworkspace = new double[n+1];
+            
+            //
+            // these initializers are not really necessary,
+            // but without them compiler complains about uninitialized locals
+            //
+            wlu = 0;
+            wul = 0;
+            
+            //
+            // Check for Errors
+            //
+            result = false;
+            errorcode = 0;
+            if( irange<=0 || irange>=4 )
+            {
+                errorcode = -4;
+            }
+            if( iorder<=0 || iorder>=3 )
+            {
+                errorcode = -5;
+            }
+            if( n<0 )
+            {
+                errorcode = -3;
+            }
+            if( irange==2 && (double)(vl)>=(double)(vu) )
+            {
+                errorcode = -6;
+            }
+            if( irange==3 && (il<1 || il>Math.Max(1, n)) )
+            {
+                errorcode = -8;
+            }
+            if( irange==3 && (iu<Math.Min(n, il) || iu>n) )
+            {
+                errorcode = -9;
+            }
+            if( errorcode!=0 )
+            {
+                return result;
+            }
+            
+            //
+            // Initialize error flags
+            //
+            ncnvrg = false;
+            toofew = false;
+            
+            //
+            // Simplifications:
+            //
+            if( (irange==3 && il==1) && iu==n )
+            {
+                irange = 1;
+            }
+            
+            //
+            // Special Case when N=1
+            //
+            if( n==1 )
+            {
+                nsplit = 1;
+                isplit[1] = 1;
+                if( irange==2 && ((double)(vl)>=(double)(d[1]) || (double)(vu)<(double)(d[1])) )
+                {
+                    m = 0;
+                }
+                else
+                {
+                    w[1] = d[1];
+                    iblock[1] = 1;
+                    m = 1;
+                }
+                result = true;
+                return result;
+            }
+            
+            //
+            // Scaling
+            //
+            t = Math.Abs(d[n]);
+            for(j=1; j<=n-1; j++)
+            {
+                t = Math.Max(t, Math.Abs(d[j]));
+                t = Math.Max(t, Math.Abs(e[j]));
+            }
+            scalefactor = 1;
+            if( (double)(t)!=(double)(0) )
+            {
+                if( (double)(t)>(double)(Math.Sqrt(Math.Sqrt(math.minrealnumber))*Math.Sqrt(math.maxrealnumber)) )
+                {
+                    scalefactor = t;
+                }
+                if( (double)(t)<(double)(Math.Sqrt(Math.Sqrt(math.maxrealnumber))*Math.Sqrt(math.minrealnumber)) )
+                {
+                    scalefactor = t;
+                }
+                for(j=1; j<=n-1; j++)
+                {
+                    d[j] = d[j]/scalefactor;
+                    e[j] = e[j]/scalefactor;
+                }
+                d[n] = d[n]/scalefactor;
+            }
+            
+            //
+            // Compute Splitting Points
+            //
+            nsplit = 1;
+            work[n] = 0;
+            pivmin = 1;
+            for(j=2; j<=n; j++)
+            {
+                tmp1 = math.sqr(e[j-1]);
+                if( (double)(Math.Abs(d[j]*d[j-1])*math.sqr(ulp)+safemn)>(double)(tmp1) )
+                {
+                    isplit[nsplit] = j-1;
+                    nsplit = nsplit+1;
+                    work[j-1] = 0;
+                }
+                else
+                {
+                    work[j-1] = tmp1;
+                    pivmin = Math.Max(pivmin, tmp1);
+                }
+            }
+            isplit[nsplit] = n;
+            pivmin = pivmin*safemn;
+            
+            //
+            // Compute Interval and ATOLI
+            //
+            if( irange==3 )
+            {
+                
+                //
+                // RANGE='I': Compute the interval containing eigenvalues
+                //     IL through IU.
+                //
+                // Compute Gershgorin interval for entire (split) matrix
+                // and use it as the initial interval
+                //
+                gu = d[1];
+                gl = d[1];
+                tmp1 = 0;
+                for(j=1; j<=n-1; j++)
+                {
+                    tmp2 = Math.Sqrt(work[j]);
+                    gu = Math.Max(gu, d[j]+tmp1+tmp2);
+                    gl = Math.Min(gl, d[j]-tmp1-tmp2);
+                    tmp1 = tmp2;
+                }
+                gu = Math.Max(gu, d[n]+tmp1);
+                gl = Math.Min(gl, d[n]-tmp1);
+                tnorm = Math.Max(Math.Abs(gl), Math.Abs(gu));
+                gl = gl-fudge*tnorm*ulp*n-fudge*2*pivmin;
+                gu = gu+fudge*tnorm*ulp*n+fudge*pivmin;
+                
+                //
+                // Compute Iteration parameters
+                //
+                itmax = (int)Math.Ceiling((Math.Log(tnorm+pivmin)-Math.Log(pivmin))/Math.Log(2))+2;
+                if( (double)(abstol)<=(double)(0) )
+                {
+                    atoli = ulp*tnorm;
+                }
+                else
+                {
+                    atoli = abstol;
+                }
+                work[n+1] = gl;
+                work[n+2] = gl;
+                work[n+3] = gu;
+                work[n+4] = gu;
+                work[n+5] = gl;
+                work[n+6] = gu;
+                iwork[1] = -1;
+                iwork[2] = -1;
+                iwork[3] = n+1;
+                iwork[4] = n+1;
+                iwork[5] = il-1;
+                iwork[6] = iu;
+                
+                //
+                // Calling DLAEBZ
+                //
+                // DLAEBZ( 3, ITMAX, N, 2, 2, NB, ATOLI, RTOLI, PIVMIN, D, E,
+                //    WORK, IWORK( 5 ), WORK( N+1 ), WORK( N+5 ), IOUT,
+                //    IWORK, W, IBLOCK, IINFO )
+                //
+                ia1s2[1] = iwork[5];
+                ia1s2[2] = iwork[6];
+                ra1s2[1] = work[n+5];
+                ra1s2[2] = work[n+6];
+                ra1s2x2[1,1] = work[n+1];
+                ra1s2x2[2,1] = work[n+2];
+                ra1s2x2[1,2] = work[n+3];
+                ra1s2x2[2,2] = work[n+4];
+                ia1s2x2[1,1] = iwork[1];
+                ia1s2x2[2,1] = iwork[2];
+                ia1s2x2[1,2] = iwork[3];
+                ia1s2x2[2,2] = iwork[4];
+                internaldlaebz(3, itmax, n, 2, 2, atoli, rtoli, pivmin, d, e, work, ref ia1s2, ref ra1s2x2, ref ra1s2, ref iout, ref ia1s2x2, ref w, ref iblock, ref iinfo);
+                iwork[5] = ia1s2[1];
+                iwork[6] = ia1s2[2];
+                work[n+5] = ra1s2[1];
+                work[n+6] = ra1s2[2];
+                work[n+1] = ra1s2x2[1,1];
+                work[n+2] = ra1s2x2[2,1];
+                work[n+3] = ra1s2x2[1,2];
+                work[n+4] = ra1s2x2[2,2];
+                iwork[1] = ia1s2x2[1,1];
+                iwork[2] = ia1s2x2[2,1];
+                iwork[3] = ia1s2x2[1,2];
+                iwork[4] = ia1s2x2[2,2];
+                if( iwork[6]==iu )
+                {
+                    wl = work[n+1];
+                    wlu = work[n+3];
+                    nwl = iwork[1];
+                    wu = work[n+4];
+                    wul = work[n+2];
+                    nwu = iwork[4];
+                }
+                else
+                {
+                    wl = work[n+2];
+                    wlu = work[n+4];
+                    nwl = iwork[2];
+                    wu = work[n+3];
+                    wul = work[n+1];
+                    nwu = iwork[3];
+                }
+                if( ((nwl<0 || nwl>=n) || nwu<1) || nwu>n )
+                {
+                    errorcode = 4;
+                    result = false;
+                    return result;
+                }
+            }
+            else
+            {
+                
+                //
+                // RANGE='A' or 'V' -- Set ATOLI
+                //
+                tnorm = Math.Max(Math.Abs(d[1])+Math.Abs(e[1]), Math.Abs(d[n])+Math.Abs(e[n-1]));
+                for(j=2; j<=n-1; j++)
+                {
+                    tnorm = Math.Max(tnorm, Math.Abs(d[j])+Math.Abs(e[j-1])+Math.Abs(e[j]));
+                }
+                if( (double)(abstol)<=(double)(0) )
+                {
+                    atoli = ulp*tnorm;
+                }
+                else
+                {
+                    atoli = abstol;
+                }
+                if( irange==2 )
+                {
+                    wl = vl;
+                    wu = vu;
+                }
+                else
+                {
+                    wl = 0;
+                    wu = 0;
+                }
+            }
+            
+            //
+            // Find Eigenvalues -- Loop Over Blocks and recompute NWL and NWU.
+            // NWL accumulates the number of eigenvalues .le. WL,
+            // NWU accumulates the number of eigenvalues .le. WU
+            //
+            m = 0;
+            iend = 0;
+            errorcode = 0;
+            nwl = 0;
+            nwu = 0;
+            for(jb=1; jb<=nsplit; jb++)
+            {
+                ioff = iend;
+                ibegin = ioff+1;
+                iend = isplit[jb];
+                iin = iend-ioff;
+                if( iin==1 )
+                {
+                    
+                    //
+                    // Special Case -- IIN=1
+                    //
+                    if( irange==1 || (double)(wl)>=(double)(d[ibegin]-pivmin) )
+                    {
+                        nwl = nwl+1;
+                    }
+                    if( irange==1 || (double)(wu)>=(double)(d[ibegin]-pivmin) )
+                    {
+                        nwu = nwu+1;
+                    }
+                    if( irange==1 || ((double)(wl)<(double)(d[ibegin]-pivmin) && (double)(wu)>=(double)(d[ibegin]-pivmin)) )
+                    {
+                        m = m+1;
+                        w[m] = d[ibegin];
+                        iblock[m] = jb;
+                    }
+                }
+                else
+                {
+                    
+                    //
+                    // General Case -- IIN > 1
+                    //
+                    // Compute Gershgorin Interval
+                    // and use it as the initial interval
+                    //
+                    gu = d[ibegin];
+                    gl = d[ibegin];
+                    tmp1 = 0;
+                    for(j=ibegin; j<=iend-1; j++)
+                    {
+                        tmp2 = Math.Abs(e[j]);
+                        gu = Math.Max(gu, d[j]+tmp1+tmp2);
+                        gl = Math.Min(gl, d[j]-tmp1-tmp2);
+                        tmp1 = tmp2;
+                    }
+                    gu = Math.Max(gu, d[iend]+tmp1);
+                    gl = Math.Min(gl, d[iend]-tmp1);
+                    bnorm = Math.Max(Math.Abs(gl), Math.Abs(gu));
+                    gl = gl-fudge*bnorm*ulp*iin-fudge*pivmin;
+                    gu = gu+fudge*bnorm*ulp*iin+fudge*pivmin;
+                    
+                    //
+                    // Compute ATOLI for the current submatrix
+                    //
+                    if( (double)(abstol)<=(double)(0) )
+                    {
+                        atoli = ulp*Math.Max(Math.Abs(gl), Math.Abs(gu));
+                    }
+                    else
+                    {
+                        atoli = abstol;
+                    }
+                    if( irange>1 )
+                    {
+                        if( (double)(gu)<(double)(wl) )
+                        {
+                            nwl = nwl+iin;
+                            nwu = nwu+iin;
+                            continue;
+                        }
+                        gl = Math.Max(gl, wl);
+                        gu = Math.Min(gu, wu);
+                        if( (double)(gl)>=(double)(gu) )
+                        {
+                            continue;
+                        }
+                    }
+                    
+                    //
+                    // Set Up Initial Interval
+                    //
+                    work[n+1] = gl;
+                    work[n+iin+1] = gu;
+                    
+                    //
+                    // Calling DLAEBZ
+                    //
+                    // CALL DLAEBZ( 1, 0, IN, IN, 1, NB, ATOLI, RTOLI, PIVMIN,
+                    //    D( IBEGIN ), E( IBEGIN ), WORK( IBEGIN ),
+                    //    IDUMMA, WORK( N+1 ), WORK( N+2*IN+1 ), IM,
+                    //    IWORK, W( M+1 ), IBLOCK( M+1 ), IINFO )
+                    //
+                    for(tmpi=1; tmpi<=iin; tmpi++)
+                    {
+                        ra1siin[tmpi] = d[ibegin-1+tmpi];
+                        if( ibegin-1+tmpi<n )
+                        {
+                            ra2siin[tmpi] = e[ibegin-1+tmpi];
+                        }
+                        ra3siin[tmpi] = work[ibegin-1+tmpi];
+                        ra1siinx2[tmpi,1] = work[n+tmpi];
+                        ra1siinx2[tmpi,2] = work[n+tmpi+iin];
+                        ra4siin[tmpi] = work[n+2*iin+tmpi];
+                        rworkspace[tmpi] = w[m+tmpi];
+                        iworkspace[tmpi] = iblock[m+tmpi];
+                        ia1siinx2[tmpi,1] = iwork[tmpi];
+                        ia1siinx2[tmpi,2] = iwork[tmpi+iin];
+                    }
+                    internaldlaebz(1, 0, iin, iin, 1, atoli, rtoli, pivmin, ra1siin, ra2siin, ra3siin, ref idumma, ref ra1siinx2, ref ra4siin, ref im, ref ia1siinx2, ref rworkspace, ref iworkspace, ref iinfo);
+                    for(tmpi=1; tmpi<=iin; tmpi++)
+                    {
+                        work[n+tmpi] = ra1siinx2[tmpi,1];
+                        work[n+tmpi+iin] = ra1siinx2[tmpi,2];
+                        work[n+2*iin+tmpi] = ra4siin[tmpi];
+                        w[m+tmpi] = rworkspace[tmpi];
+                        iblock[m+tmpi] = iworkspace[tmpi];
+                        iwork[tmpi] = ia1siinx2[tmpi,1];
+                        iwork[tmpi+iin] = ia1siinx2[tmpi,2];
+                    }
+                    nwl = nwl+iwork[1];
+                    nwu = nwu+iwork[iin+1];
+                    iwoff = m-iwork[1];
+                    
+                    //
+                    // Compute Eigenvalues
+                    //
+                    itmax = (int)Math.Ceiling((Math.Log(gu-gl+pivmin)-Math.Log(pivmin))/Math.Log(2))+2;
+                    
+                    //
+                    // Calling DLAEBZ
+                    //
+                    //CALL DLAEBZ( 2, ITMAX, IN, IN, 1, NB, ATOLI, RTOLI, PIVMIN,
+                    //    D( IBEGIN ), E( IBEGIN ), WORK( IBEGIN ),
+                    //    IDUMMA, WORK( N+1 ), WORK( N+2*IN+1 ), IOUT,
+                    //    IWORK, W( M+1 ), IBLOCK( M+1 ), IINFO )
+                    //
+                    for(tmpi=1; tmpi<=iin; tmpi++)
+                    {
+                        ra1siin[tmpi] = d[ibegin-1+tmpi];
+                        if( ibegin-1+tmpi<n )
+                        {
+                            ra2siin[tmpi] = e[ibegin-1+tmpi];
+                        }
+                        ra3siin[tmpi] = work[ibegin-1+tmpi];
+                        ra1siinx2[tmpi,1] = work[n+tmpi];
+                        ra1siinx2[tmpi,2] = work[n+tmpi+iin];
+                        ra4siin[tmpi] = work[n+2*iin+tmpi];
+                        rworkspace[tmpi] = w[m+tmpi];
+                        iworkspace[tmpi] = iblock[m+tmpi];
+                        ia1siinx2[tmpi,1] = iwork[tmpi];
+                        ia1siinx2[tmpi,2] = iwork[tmpi+iin];
+                    }
+                    internaldlaebz(2, itmax, iin, iin, 1, atoli, rtoli, pivmin, ra1siin, ra2siin, ra3siin, ref idumma, ref ra1siinx2, ref ra4siin, ref iout, ref ia1siinx2, ref rworkspace, ref iworkspace, ref iinfo);
+                    for(tmpi=1; tmpi<=iin; tmpi++)
+                    {
+                        work[n+tmpi] = ra1siinx2[tmpi,1];
+                        work[n+tmpi+iin] = ra1siinx2[tmpi,2];
+                        work[n+2*iin+tmpi] = ra4siin[tmpi];
+                        w[m+tmpi] = rworkspace[tmpi];
+                        iblock[m+tmpi] = iworkspace[tmpi];
+                        iwork[tmpi] = ia1siinx2[tmpi,1];
+                        iwork[tmpi+iin] = ia1siinx2[tmpi,2];
+                    }
+                    
+                    //
+                    // Copy Eigenvalues Into W and IBLOCK
+                    // Use -JB for block number for unconverged eigenvalues.
+                    //
+                    for(j=1; j<=iout; j++)
+                    {
+                        tmp1 = 0.5*(work[j+n]+work[j+iin+n]);
+                        
+                        //
+                        // Flag non-convergence.
+                        //
+                        if( j>iout-iinfo )
+                        {
+                            ncnvrg = true;
+                            ib = -jb;
+                        }
+                        else
+                        {
+                            ib = jb;
+                        }
+                        for(je=iwork[j]+1+iwoff; je<=iwork[j+iin]+iwoff; je++)
+                        {
+                            w[je] = tmp1;
+                            iblock[je] = ib;
+                        }
+                    }
+                    m = m+im;
+                }
+            }
+            
+            //
+            // If RANGE='I', then (WL,WU) contains eigenvalues NWL+1,...,NWU
+            // If NWL+1 < IL or NWU > IU, discard extra eigenvalues.
+            //
+            if( irange==3 )
+            {
+                im = 0;
+                idiscl = il-1-nwl;
+                idiscu = nwu-iu;
+                if( idiscl>0 || idiscu>0 )
+                {
+                    for(je=1; je<=m; je++)
+                    {
+                        if( (double)(w[je])<=(double)(wlu) && idiscl>0 )
+                        {
+                            idiscl = idiscl-1;
+                        }
+                        else
+                        {
+                            if( (double)(w[je])>=(double)(wul) && idiscu>0 )
+                            {
+                                idiscu = idiscu-1;
+                            }
+                            else
+                            {
+                                im = im+1;
+                                w[im] = w[je];
+                                iblock[im] = iblock[je];
+                            }
+                        }
+                    }
+                    m = im;
+                }
+                if( idiscl>0 || idiscu>0 )
+                {
+                    
+                    //
+                    // Code to deal with effects of bad arithmetic:
+                    // Some low eigenvalues to be discarded are not in (WL,WLU],
+                    // or high eigenvalues to be discarded are not in (WUL,WU]
+                    // so just kill off the smallest IDISCL/largest IDISCU
+                    // eigenvalues, by simply finding the smallest/largest
+                    // eigenvalue(s).
+                    //
+                    // (If N(w) is monotone non-decreasing, this should never
+                    //  happen.)
+                    //
+                    if( idiscl>0 )
+                    {
+                        wkill = wu;
+                        for(jdisc=1; jdisc<=idiscl; jdisc++)
+                        {
+                            iw = 0;
+                            for(je=1; je<=m; je++)
+                            {
+                                if( iblock[je]!=0 && ((double)(w[je])<(double)(wkill) || iw==0) )
+                                {
+                                    iw = je;
+                                    wkill = w[je];
+                                }
+                            }
+                            iblock[iw] = 0;
+                        }
+                    }
+                    if( idiscu>0 )
+                    {
+                        wkill = wl;
+                        for(jdisc=1; jdisc<=idiscu; jdisc++)
+                        {
+                            iw = 0;
+                            for(je=1; je<=m; je++)
+                            {
+                                if( iblock[je]!=0 && ((double)(w[je])>(double)(wkill) || iw==0) )
+                                {
+                                    iw = je;
+                                    wkill = w[je];
+                                }
+                            }
+                            iblock[iw] = 0;
+                        }
+                    }
+                    im = 0;
+                    for(je=1; je<=m; je++)
+                    {
+                        if( iblock[je]!=0 )
+                        {
+                            im = im+1;
+                            w[im] = w[je];
+                            iblock[im] = iblock[je];
+                        }
+                    }
+                    m = im;
+                }
+                if( idiscl<0 || idiscu<0 )
+                {
+                    toofew = true;
+                }
+            }
+            
+            //
+            // If ORDER='B', do nothing -- the eigenvalues are already sorted
+            //    by block.
+            // If ORDER='E', sort the eigenvalues from smallest to largest
+            //
+            if( iorder==1 && nsplit>1 )
+            {
+                for(je=1; je<=m-1; je++)
+                {
+                    ie = 0;
+                    tmp1 = w[je];
+                    for(j=je+1; j<=m; j++)
+                    {
+                        if( (double)(w[j])<(double)(tmp1) )
+                        {
+                            ie = j;
+                            tmp1 = w[j];
+                        }
+                    }
+                    if( ie!=0 )
+                    {
+                        itmp1 = iblock[ie];
+                        w[ie] = w[je];
+                        iblock[ie] = iblock[je];
+                        w[je] = tmp1;
+                        iblock[je] = itmp1;
+                    }
+                }
+            }
+            for(j=1; j<=m; j++)
+            {
+                w[j] = w[j]*scalefactor;
+            }
+            errorcode = 0;
+            if( ncnvrg )
+            {
+                errorcode = errorcode+1;
+            }
+            if( toofew )
+            {
+                errorcode = errorcode+2;
+            }
+            result = errorcode==0;
+            return result;
+        }
+
+
+        private static void internaldstein(int n,
+            double[] d,
+            double[] e,
+            int m,
+            double[] w,
+            int[] iblock,
+            int[] isplit,
+            ref double[,] z,
+            ref int[] ifail,
+            ref int info)
+        {
+            int maxits = 0;
+            int extra = 0;
+            int b1 = 0;
+            int blksiz = 0;
+            int bn = 0;
+            int gpind = 0;
+            int i = 0;
+            int iinfo = 0;
+            int its = 0;
+            int j = 0;
+            int j1 = 0;
+            int jblk = 0;
+            int jmax = 0;
+            int nblk = 0;
+            int nrmchk = 0;
+            double dtpcrt = 0;
+            double eps = 0;
+            double eps1 = 0;
+            double nrm = 0;
+            double onenrm = 0;
+            double ortol = 0;
+            double pertol = 0;
+            double scl = 0;
+            double sep = 0;
+            double tol = 0;
+            double xj = 0;
+            double xjm = 0;
+            double ztr = 0;
+            double[] work1 = new double[0];
+            double[] work2 = new double[0];
+            double[] work3 = new double[0];
+            double[] work4 = new double[0];
+            double[] work5 = new double[0];
+            int[] iwork = new int[0];
+            bool tmpcriterion = new bool();
+            int ti = 0;
+            int i1 = 0;
+            int i2 = 0;
+            double v = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            e = (double[])e.Clone();
+            w = (double[])w.Clone();
+            z = new double[0,0];
+            ifail = new int[0];
+            info = 0;
+
+            maxits = 5;
+            extra = 2;
+            work1 = new double[Math.Max(n, 1)+1];
+            work2 = new double[Math.Max(n-1, 1)+1];
+            work3 = new double[Math.Max(n, 1)+1];
+            work4 = new double[Math.Max(n, 1)+1];
+            work5 = new double[Math.Max(n, 1)+1];
+            iwork = new int[Math.Max(n, 1)+1];
+            ifail = new int[Math.Max(m, 1)+1];
+            z = new double[Math.Max(n, 1)+1, Math.Max(m, 1)+1];
+            
+            //
+            // these initializers are not really necessary,
+            // but without them compiler complains about uninitialized locals
+            //
+            gpind = 0;
+            onenrm = 0;
+            ortol = 0;
+            dtpcrt = 0;
+            xjm = 0;
+            
+            //
+            // Test the input parameters.
+            //
+            info = 0;
+            for(i=1; i<=m; i++)
+            {
+                ifail[i] = 0;
+            }
+            if( n<0 )
+            {
+                info = -1;
+                return;
+            }
+            if( m<0 || m>n )
+            {
+                info = -4;
+                return;
+            }
+            for(j=2; j<=m; j++)
+            {
+                if( iblock[j]<iblock[j-1] )
+                {
+                    info = -6;
+                    break;
+                }
+                if( iblock[j]==iblock[j-1] && (double)(w[j])<(double)(w[j-1]) )
+                {
+                    info = -5;
+                    break;
+                }
+            }
+            if( info!=0 )
+            {
+                return;
+            }
+            
+            //
+            // Quick return if possible
+            //
+            if( n==0 || m==0 )
+            {
+                return;
+            }
+            if( n==1 )
+            {
+                z[1,1] = 1;
+                return;
+            }
+            
+            //
+            // Some preparations
+            //
+            ti = n-1;
+            for(i_=1; i_<=ti;i_++)
+            {
+                work1[i_] = e[i_];
+            }
+            e = new double[n+1];
+            for(i_=1; i_<=ti;i_++)
+            {
+                e[i_] = work1[i_];
+            }
+            for(i_=1; i_<=m;i_++)
+            {
+                work1[i_] = w[i_];
+            }
+            w = new double[n+1];
+            for(i_=1; i_<=m;i_++)
+            {
+                w[i_] = work1[i_];
+            }
+            
+            //
+            // Get machine constants.
+            //
+            eps = math.machineepsilon;
+            
+            //
+            // Compute eigenvectors of matrix blocks.
+            //
+            j1 = 1;
+            for(nblk=1; nblk<=iblock[m]; nblk++)
+            {
+                
+                //
+                // Find starting and ending indices of block nblk.
+                //
+                if( nblk==1 )
+                {
+                    b1 = 1;
+                }
+                else
+                {
+                    b1 = isplit[nblk-1]+1;
+                }
+                bn = isplit[nblk];
+                blksiz = bn-b1+1;
+                if( blksiz!=1 )
+                {
+                    
+                    //
+                    // Compute reorthogonalization criterion and stopping criterion.
+                    //
+                    gpind = b1;
+                    onenrm = Math.Abs(d[b1])+Math.Abs(e[b1]);
+                    onenrm = Math.Max(onenrm, Math.Abs(d[bn])+Math.Abs(e[bn-1]));
+                    for(i=b1+1; i<=bn-1; i++)
+                    {
+                        onenrm = Math.Max(onenrm, Math.Abs(d[i])+Math.Abs(e[i-1])+Math.Abs(e[i]));
+                    }
+                    ortol = 0.001*onenrm;
+                    dtpcrt = Math.Sqrt(0.1/blksiz);
+                }
+                
+                //
+                // Loop through eigenvalues of block nblk.
+                //
+                jblk = 0;
+                for(j=j1; j<=m; j++)
+                {
+                    if( iblock[j]!=nblk )
+                    {
+                        j1 = j;
+                        break;
+                    }
+                    jblk = jblk+1;
+                    xj = w[j];
+                    if( blksiz==1 )
+                    {
+                        
+                        //
+                        // Skip all the work if the block size is one.
+                        //
+                        work1[1] = 1;
+                    }
+                    else
+                    {
+                        
+                        //
+                        // If eigenvalues j and j-1 are too close, add a relatively
+                        // small perturbation.
+                        //
+                        if( jblk>1 )
+                        {
+                            eps1 = Math.Abs(eps*xj);
+                            pertol = 10*eps1;
+                            sep = xj-xjm;
+                            if( (double)(sep)<(double)(pertol) )
+                            {
+                                xj = xjm+pertol;
+                            }
+                        }
+                        its = 0;
+                        nrmchk = 0;
+                        
+                        //
+                        // Get random starting vector.
+                        //
+                        for(ti=1; ti<=blksiz; ti++)
+                        {
+                            work1[ti] = 2*math.randomreal()-1;
+                        }
+                        
+                        //
+                        // Copy the matrix T so it won't be destroyed in factorization.
+                        //
+                        for(ti=1; ti<=blksiz-1; ti++)
+                        {
+                            work2[ti] = e[b1+ti-1];
+                            work3[ti] = e[b1+ti-1];
+                            work4[ti] = d[b1+ti-1];
+                        }
+                        work4[blksiz] = d[b1+blksiz-1];
+                        
+                        //
+                        // Compute LU factors with partial pivoting  ( PT = LU )
+                        //
+                        tol = 0;
+                        tdininternaldlagtf(blksiz, ref work4, xj, ref work2, ref work3, tol, ref work5, ref iwork, ref iinfo);
+                        
+                        //
+                        // Update iteration count.
+                        //
+                        do
+                        {
+                            its = its+1;
+                            if( its>maxits )
+                            {
+                                
+                                //
+                                // If stopping criterion was not satisfied, update info and
+                                // store eigenvector number in array ifail.
+                                //
+                                info = info+1;
+                                ifail[info] = j;
+                                break;
+                            }
+                            
+                            //
+                            // Normalize and scale the righthand side vector Pb.
+                            //
+                            v = 0;
+                            for(ti=1; ti<=blksiz; ti++)
+                            {
+                                v = v+Math.Abs(work1[ti]);
+                            }
+                            scl = blksiz*onenrm*Math.Max(eps, Math.Abs(work4[blksiz]))/v;
+                            for(i_=1; i_<=blksiz;i_++)
+                            {
+                                work1[i_] = scl*work1[i_];
+                            }
+                            
+                            //
+                            // Solve the system LU = Pb.
+                            //
+                            tdininternaldlagts(blksiz, work4, work2, work3, work5, iwork, ref work1, ref tol, ref iinfo);
+                            
+                            //
+                            // Reorthogonalize by modified Gram-Schmidt if eigenvalues are
+                            // close enough.
+                            //
+                            if( jblk!=1 )
+                            {
+                                if( (double)(Math.Abs(xj-xjm))>(double)(ortol) )
+                                {
+                                    gpind = j;
+                                }
+                                if( gpind!=j )
+                                {
+                                    for(i=gpind; i<=j-1; i++)
+                                    {
+                                        i1 = b1;
+                                        i2 = b1+blksiz-1;
+                                        i1_ = (i1)-(1);
+                                        ztr = 0.0;
+                                        for(i_=1; i_<=blksiz;i_++)
+                                        {
+                                            ztr += work1[i_]*z[i_+i1_,i];
+                                        }
+                                        i1_ = (i1) - (1);
+                                        for(i_=1; i_<=blksiz;i_++)
+                                        {
+                                            work1[i_] = work1[i_] - ztr*z[i_+i1_,i];
+                                        }
+                                    }
+                                }
+                            }
+                            
+                            //
+                            // Check the infinity norm of the iterate.
+                            //
+                            jmax = blas.vectoridxabsmax(work1, 1, blksiz);
+                            nrm = Math.Abs(work1[jmax]);
+                            
+                            //
+                            // Continue for additional iterations after norm reaches
+                            // stopping criterion.
+                            //
+                            tmpcriterion = false;
+                            if( (double)(nrm)<(double)(dtpcrt) )
+                            {
+                                tmpcriterion = true;
+                            }
+                            else
+                            {
+                                nrmchk = nrmchk+1;
+                                if( nrmchk<extra+1 )
+                                {
+                                    tmpcriterion = true;
+                                }
+                            }
+                        }
+                        while( tmpcriterion );
+                        
+                        //
+                        // Accept iterate as jth eigenvector.
+                        //
+                        scl = 1/blas.vectornorm2(work1, 1, blksiz);
+                        jmax = blas.vectoridxabsmax(work1, 1, blksiz);
+                        if( (double)(work1[jmax])<(double)(0) )
+                        {
+                            scl = -scl;
+                        }
+                        for(i_=1; i_<=blksiz;i_++)
+                        {
+                            work1[i_] = scl*work1[i_];
+                        }
+                    }
+                    for(i=1; i<=n; i++)
+                    {
+                        z[i,j] = 0;
+                    }
+                    for(i=1; i<=blksiz; i++)
+                    {
+                        z[b1+i-1,j] = work1[i];
+                    }
+                    
+                    //
+                    // Save the shift to check eigenvalue spacing at next
+                    // iteration.
+                    //
+                    xjm = xj;
+                }
+            }
+        }
+
+
+        private static void tdininternaldlagtf(int n,
+            ref double[] a,
+            double lambdav,
+            ref double[] b,
+            ref double[] c,
+            double tol,
+            ref double[] d,
+            ref int[] iin,
+            ref int info)
+        {
+            int k = 0;
+            double eps = 0;
+            double mult = 0;
+            double piv1 = 0;
+            double piv2 = 0;
+            double scale1 = 0;
+            double scale2 = 0;
+            double temp = 0;
+            double tl = 0;
+
+            info = 0;
+
+            info = 0;
+            if( n<0 )
+            {
+                info = -1;
+                return;
+            }
+            if( n==0 )
+            {
+                return;
+            }
+            a[1] = a[1]-lambdav;
+            iin[n] = 0;
+            if( n==1 )
+            {
+                if( (double)(a[1])==(double)(0) )
+                {
+                    iin[1] = 1;
+                }
+                return;
+            }
+            eps = math.machineepsilon;
+            tl = Math.Max(tol, eps);
+            scale1 = Math.Abs(a[1])+Math.Abs(b[1]);
+            for(k=1; k<=n-1; k++)
+            {
+                a[k+1] = a[k+1]-lambdav;
+                scale2 = Math.Abs(c[k])+Math.Abs(a[k+1]);
+                if( k<n-1 )
+                {
+                    scale2 = scale2+Math.Abs(b[k+1]);
+                }
+                if( (double)(a[k])==(double)(0) )
+                {
+                    piv1 = 0;
+                }
+                else
+                {
+                    piv1 = Math.Abs(a[k])/scale1;
+                }
+                if( (double)(c[k])==(double)(0) )
+                {
+                    iin[k] = 0;
+                    piv2 = 0;
+                    scale1 = scale2;
+                    if( k<n-1 )
+                    {
+                        d[k] = 0;
+                    }
+                }
+                else
+                {
+                    piv2 = Math.Abs(c[k])/scale2;
+                    if( (double)(piv2)<=(double)(piv1) )
+                    {
+                        iin[k] = 0;
+                        scale1 = scale2;
+                        c[k] = c[k]/a[k];
+                        a[k+1] = a[k+1]-c[k]*b[k];
+                        if( k<n-1 )
+                        {
+                            d[k] = 0;
+                        }
+                    }
+                    else
+                    {
+                        iin[k] = 1;
+                        mult = a[k]/c[k];
+                        a[k] = c[k];
+                        temp = a[k+1];
+                        a[k+1] = b[k]-mult*temp;
+                        if( k<n-1 )
+                        {
+                            d[k] = b[k+1];
+                            b[k+1] = -(mult*d[k]);
+                        }
+                        b[k] = temp;
+                        c[k] = mult;
+                    }
+                }
+                if( (double)(Math.Max(piv1, piv2))<=(double)(tl) && iin[n]==0 )
+                {
+                    iin[n] = k;
+                }
+            }
+            if( (double)(Math.Abs(a[n]))<=(double)(scale1*tl) && iin[n]==0 )
+            {
+                iin[n] = n;
+            }
+        }
+
+
+        private static void tdininternaldlagts(int n,
+            double[] a,
+            double[] b,
+            double[] c,
+            double[] d,
+            int[] iin,
+            ref double[] y,
+            ref double tol,
+            ref int info)
+        {
+            int k = 0;
+            double absak = 0;
+            double ak = 0;
+            double bignum = 0;
+            double eps = 0;
+            double pert = 0;
+            double sfmin = 0;
+            double temp = 0;
+
+            info = 0;
+
+            info = 0;
+            if( n<0 )
+            {
+                info = -1;
+                return;
+            }
+            if( n==0 )
+            {
+                return;
+            }
+            eps = math.machineepsilon;
+            sfmin = math.minrealnumber;
+            bignum = 1/sfmin;
+            if( (double)(tol)<=(double)(0) )
+            {
+                tol = Math.Abs(a[1]);
+                if( n>1 )
+                {
+                    tol = Math.Max(tol, Math.Max(Math.Abs(a[2]), Math.Abs(b[1])));
+                }
+                for(k=3; k<=n; k++)
+                {
+                    tol = Math.Max(tol, Math.Max(Math.Abs(a[k]), Math.Max(Math.Abs(b[k-1]), Math.Abs(d[k-2]))));
+                }
+                tol = tol*eps;
+                if( (double)(tol)==(double)(0) )
+                {
+                    tol = eps;
+                }
+            }
+            for(k=2; k<=n; k++)
+            {
+                if( iin[k-1]==0 )
+                {
+                    y[k] = y[k]-c[k-1]*y[k-1];
+                }
+                else
+                {
+                    temp = y[k-1];
+                    y[k-1] = y[k];
+                    y[k] = temp-c[k-1]*y[k];
+                }
+            }
+            for(k=n; k>=1; k--)
+            {
+                if( k<=n-2 )
+                {
+                    temp = y[k]-b[k]*y[k+1]-d[k]*y[k+2];
+                }
+                else
+                {
+                    if( k==n-1 )
+                    {
+                        temp = y[k]-b[k]*y[k+1];
+                    }
+                    else
+                    {
+                        temp = y[k];
+                    }
+                }
+                ak = a[k];
+                pert = Math.Abs(tol);
+                if( (double)(ak)<(double)(0) )
+                {
+                    pert = -pert;
+                }
+                while( true )
+                {
+                    absak = Math.Abs(ak);
+                    if( (double)(absak)<(double)(1) )
+                    {
+                        if( (double)(absak)<(double)(sfmin) )
+                        {
+                            if( (double)(absak)==(double)(0) || (double)(Math.Abs(temp)*sfmin)>(double)(absak) )
+                            {
+                                ak = ak+pert;
+                                pert = 2*pert;
+                                continue;
+                            }
+                            else
+                            {
+                                temp = temp*bignum;
+                                ak = ak*bignum;
+                            }
+                        }
+                        else
+                        {
+                            if( (double)(Math.Abs(temp))>(double)(absak*bignum) )
+                            {
+                                ak = ak+pert;
+                                pert = 2*pert;
+                                continue;
+                            }
+                        }
+                    }
+                    break;
+                }
+                y[k] = temp/ak;
+            }
+        }
+
+
+        private static void internaldlaebz(int ijob,
+            int nitmax,
+            int n,
+            int mmax,
+            int minp,
+            double abstol,
+            double reltol,
+            double pivmin,
+            double[] d,
+            double[] e,
+            double[] e2,
+            ref int[] nval,
+            ref double[,] ab,
+            ref double[] c,
+            ref int mout,
+            ref int[,] nab,
+            ref double[] work,
+            ref int[] iwork,
+            ref int info)
+        {
+            int itmp1 = 0;
+            int itmp2 = 0;
+            int j = 0;
+            int ji = 0;
+            int jit = 0;
+            int jp = 0;
+            int kf = 0;
+            int kfnew = 0;
+            int kl = 0;
+            int klnew = 0;
+            double tmp1 = 0;
+            double tmp2 = 0;
+
+            mout = 0;
+            info = 0;
+
+            info = 0;
+            if( ijob<1 || ijob>3 )
+            {
+                info = -1;
+                return;
+            }
+            
+            //
+            // Initialize NAB
+            //
+            if( ijob==1 )
+            {
+                
+                //
+                // Compute the number of eigenvalues in the initial intervals.
+                //
+                mout = 0;
+                
+                //
+                //DIR$ NOVECTOR
+                //
+                for(ji=1; ji<=minp; ji++)
+                {
+                    for(jp=1; jp<=2; jp++)
+                    {
+                        tmp1 = d[1]-ab[ji,jp];
+                        if( (double)(Math.Abs(tmp1))<(double)(pivmin) )
+                        {
+                            tmp1 = -pivmin;
+                        }
+                        nab[ji,jp] = 0;
+                        if( (double)(tmp1)<=(double)(0) )
+                        {
+                            nab[ji,jp] = 1;
+                        }
+                        for(j=2; j<=n; j++)
+                        {
+                            tmp1 = d[j]-e2[j-1]/tmp1-ab[ji,jp];
+                            if( (double)(Math.Abs(tmp1))<(double)(pivmin) )
+                            {
+                                tmp1 = -pivmin;
+                            }
+                            if( (double)(tmp1)<=(double)(0) )
+                            {
+                                nab[ji,jp] = nab[ji,jp]+1;
+                            }
+                        }
+                    }
+                    mout = mout+nab[ji,2]-nab[ji,1];
+                }
+                return;
+            }
+            
+            //
+            // Initialize for loop
+            //
+            // KF and KL have the following meaning:
+            //   Intervals 1,...,KF-1 have converged.
+            //   Intervals KF,...,KL  still need to be refined.
+            //
+            kf = 1;
+            kl = minp;
+            
+            //
+            // If IJOB=2, initialize C.
+            // If IJOB=3, use the user-supplied starting point.
+            //
+            if( ijob==2 )
+            {
+                for(ji=1; ji<=minp; ji++)
+                {
+                    c[ji] = 0.5*(ab[ji,1]+ab[ji,2]);
+                }
+            }
+            
+            //
+            // Iteration loop
+            //
+            for(jit=1; jit<=nitmax; jit++)
+            {
+                
+                //
+                // Loop over intervals
+                //
+                //
+                // Serial Version of the loop
+                //
+                klnew = kl;
+                for(ji=kf; ji<=kl; ji++)
+                {
+                    
+                    //
+                    // Compute N(w), the number of eigenvalues less than w
+                    //
+                    tmp1 = c[ji];
+                    tmp2 = d[1]-tmp1;
+                    itmp1 = 0;
+                    if( (double)(tmp2)<=(double)(pivmin) )
+                    {
+                        itmp1 = 1;
+                        tmp2 = Math.Min(tmp2, -pivmin);
+                    }
+                    
+                    //
+                    // A series of compiler directives to defeat vectorization
+                    // for the next loop
+                    //
+                    //*$PL$ CMCHAR=' '
+                    //CDIR$          NEXTSCALAR
+                    //C$DIR          SCALAR
+                    //CDIR$          NEXT SCALAR
+                    //CVD$L          NOVECTOR
+                    //CDEC$          NOVECTOR
+                    //CVD$           NOVECTOR
+                    //*VDIR          NOVECTOR
+                    //*VOCL          LOOP,SCALAR
+                    //CIBM           PREFER SCALAR
+                    //*$PL$ CMCHAR='*'
+                    //
+                    for(j=2; j<=n; j++)
+                    {
+                        tmp2 = d[j]-e2[j-1]/tmp2-tmp1;
+                        if( (double)(tmp2)<=(double)(pivmin) )
+                        {
+                            itmp1 = itmp1+1;
+                            tmp2 = Math.Min(tmp2, -pivmin);
+                        }
+                    }
+                    if( ijob<=2 )
+                    {
+                        
+                        //
+                        // IJOB=2: Choose all intervals containing eigenvalues.
+                        //
+                        // Insure that N(w) is monotone
+                        //
+                        itmp1 = Math.Min(nab[ji,2], Math.Max(nab[ji,1], itmp1));
+                        
+                        //
+                        // Update the Queue -- add intervals if both halves
+                        // contain eigenvalues.
+                        //
+                        if( itmp1==nab[ji,2] )
+                        {
+                            
+                            //
+                            // No eigenvalue in the upper interval:
+                            // just use the lower interval.
+                            //
+                            ab[ji,2] = tmp1;
+                        }
+                        else
+                        {
+                            if( itmp1==nab[ji,1] )
+                            {
+                                
+                                //
+                                // No eigenvalue in the lower interval:
+                                // just use the upper interval.
+                                //
+                                ab[ji,1] = tmp1;
+                            }
+                            else
+                            {
+                                if( klnew<mmax )
+                                {
+                                    
+                                    //
+                                    // Eigenvalue in both intervals -- add upper to queue.
+                                    //
+                                    klnew = klnew+1;
+                                    ab[klnew,2] = ab[ji,2];
+                                    nab[klnew,2] = nab[ji,2];
+                                    ab[klnew,1] = tmp1;
+                                    nab[klnew,1] = itmp1;
+                                    ab[ji,2] = tmp1;
+                                    nab[ji,2] = itmp1;
+                                }
+                                else
+                                {
+                                    info = mmax+1;
+                                    return;
+                                }
+                            }
+                        }
+                    }
+                    else
+                    {
+                        
+                        //
+                        // IJOB=3: Binary search.  Keep only the interval
+                        // containing  w  s.t. N(w) = NVAL
+                        //
+                        if( itmp1<=nval[ji] )
+                        {
+                            ab[ji,1] = tmp1;
+                            nab[ji,1] = itmp1;
+                        }
+                        if( itmp1>=nval[ji] )
+                        {
+                            ab[ji,2] = tmp1;
+                            nab[ji,2] = itmp1;
+                        }
+                    }
+                }
+                kl = klnew;
+                
+                //
+                // Check for convergence
+                //
+                kfnew = kf;
+                for(ji=kf; ji<=kl; ji++)
+                {
+                    tmp1 = Math.Abs(ab[ji,2]-ab[ji,1]);
+                    tmp2 = Math.Max(Math.Abs(ab[ji,2]), Math.Abs(ab[ji,1]));
+                    if( (double)(tmp1)<(double)(Math.Max(abstol, Math.Max(pivmin, reltol*tmp2))) || nab[ji,1]>=nab[ji,2] )
+                    {
+                        
+                        //
+                        // Converged -- Swap with position KFNEW,
+                        // then increment KFNEW
+                        //
+                        if( ji>kfnew )
+                        {
+                            tmp1 = ab[ji,1];
+                            tmp2 = ab[ji,2];
+                            itmp1 = nab[ji,1];
+                            itmp2 = nab[ji,2];
+                            ab[ji,1] = ab[kfnew,1];
+                            ab[ji,2] = ab[kfnew,2];
+                            nab[ji,1] = nab[kfnew,1];
+                            nab[ji,2] = nab[kfnew,2];
+                            ab[kfnew,1] = tmp1;
+                            ab[kfnew,2] = tmp2;
+                            nab[kfnew,1] = itmp1;
+                            nab[kfnew,2] = itmp2;
+                            if( ijob==3 )
+                            {
+                                itmp1 = nval[ji];
+                                nval[ji] = nval[kfnew];
+                                nval[kfnew] = itmp1;
+                            }
+                        }
+                        kfnew = kfnew+1;
+                    }
+                }
+                kf = kfnew;
+                
+                //
+                // Choose Midpoints
+                //
+                for(ji=kf; ji<=kl; ji++)
+                {
+                    c[ji] = 0.5*(ab[ji,1]+ab[ji,2]);
+                }
+                
+                //
+                // If no more intervals to refine, quit.
+                //
+                if( kf>kl )
+                {
+                    break;
+                }
+            }
+            
+            //
+            // Converged
+            //
+            info = Math.Max(kl+1-kf, 0);
+            mout = kl;
+        }
+
+
+        /*************************************************************************
+        Internal subroutine
+
+          -- LAPACK routine (version 3.0) --
+             Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd.,
+             Courant Institute, Argonne National Lab, and Rice University
+             June 30, 1999
+        *************************************************************************/
+        private static void internaltrevc(double[,] t,
+            int n,
+            int side,
+            int howmny,
+            bool[] vselect,
+            ref double[,] vl,
+            ref double[,] vr,
+            ref int m,
+            ref int info)
+        {
+            bool allv = new bool();
+            bool bothv = new bool();
+            bool leftv = new bool();
+            bool over = new bool();
+            bool pair = new bool();
+            bool rightv = new bool();
+            bool somev = new bool();
+            int i = 0;
+            int ierr = 0;
+            int ii = 0;
+            int ip = 0;
+            int iis = 0;
+            int j = 0;
+            int j1 = 0;
+            int j2 = 0;
+            int jnxt = 0;
+            int k = 0;
+            int ki = 0;
+            int n2 = 0;
+            double beta = 0;
+            double bignum = 0;
+            double emax = 0;
+            double ovfl = 0;
+            double rec = 0;
+            double remax = 0;
+            double scl = 0;
+            double smin = 0;
+            double smlnum = 0;
+            double ulp = 0;
+            double unfl = 0;
+            double vcrit = 0;
+            double vmax = 0;
+            double wi = 0;
+            double wr = 0;
+            double xnorm = 0;
+            double[,] x = new double[0,0];
+            double[] work = new double[0];
+            double[] temp = new double[0];
+            double[,] temp11 = new double[0,0];
+            double[,] temp22 = new double[0,0];
+            double[,] temp11b = new double[0,0];
+            double[,] temp21b = new double[0,0];
+            double[,] temp12b = new double[0,0];
+            double[,] temp22b = new double[0,0];
+            bool skipflag = new bool();
+            int k1 = 0;
+            int k2 = 0;
+            int k3 = 0;
+            int k4 = 0;
+            double vt = 0;
+            bool[] rswap4 = new bool[0];
+            bool[] zswap4 = new bool[0];
+            int[,] ipivot44 = new int[0,0];
+            double[] civ4 = new double[0];
+            double[] crv4 = new double[0];
+            int i_ = 0;
+            int i1_ = 0;
+
+            vselect = (bool[])vselect.Clone();
+            m = 0;
+            info = 0;
+
+            x = new double[2+1, 2+1];
+            temp11 = new double[1+1, 1+1];
+            temp11b = new double[1+1, 1+1];
+            temp21b = new double[2+1, 1+1];
+            temp12b = new double[1+1, 2+1];
+            temp22b = new double[2+1, 2+1];
+            temp22 = new double[2+1, 2+1];
+            work = new double[3*n+1];
+            temp = new double[n+1];
+            rswap4 = new bool[4+1];
+            zswap4 = new bool[4+1];
+            ipivot44 = new int[4+1, 4+1];
+            civ4 = new double[4+1];
+            crv4 = new double[4+1];
+            if( howmny!=1 )
+            {
+                if( side==1 || side==3 )
+                {
+                    vr = new double[n+1, n+1];
+                }
+                if( side==2 || side==3 )
+                {
+                    vl = new double[n+1, n+1];
+                }
+            }
+            
+            //
+            // Decode and test the input parameters
+            //
+            bothv = side==3;
+            rightv = side==1 || bothv;
+            leftv = side==2 || bothv;
+            allv = howmny==2;
+            over = howmny==1;
+            somev = howmny==3;
+            info = 0;
+            if( n<0 )
+            {
+                info = -2;
+                return;
+            }
+            if( !rightv && !leftv )
+            {
+                info = -3;
+                return;
+            }
+            if( (!allv && !over) && !somev )
+            {
+                info = -4;
+                return;
+            }
+            
+            //
+            // Set M to the number of columns required to store the selected
+            // eigenvectors, standardize the array SELECT if necessary, and
+            // test MM.
+            //
+            if( somev )
+            {
+                m = 0;
+                pair = false;
+                for(j=1; j<=n; j++)
+                {
+                    if( pair )
+                    {
+                        pair = false;
+                        vselect[j] = false;
+                    }
+                    else
+                    {
+                        if( j<n )
+                        {
+                            if( (double)(t[j+1,j])==(double)(0) )
+                            {
+                                if( vselect[j] )
+                                {
+                                    m = m+1;
+                                }
+                            }
+                            else
+                            {
+                                pair = true;
+                                if( vselect[j] || vselect[j+1] )
+                                {
+                                    vselect[j] = true;
+                                    m = m+2;
+                                }
+                            }
+                        }
+                        else
+                        {
+                            if( vselect[n] )
+                            {
+                                m = m+1;
+                            }
+                        }
+                    }
+                }
+            }
+            else
+            {
+                m = n;
+            }
+            
+            //
+            // Quick return if possible.
+            //
+            if( n==0 )
+            {
+                return;
+            }
+            
+            //
+            // Set the constants to control overflow.
+            //
+            unfl = math.minrealnumber;
+            ovfl = 1/unfl;
+            ulp = math.machineepsilon;
+            smlnum = unfl*(n/ulp);
+            bignum = (1-ulp)/smlnum;
+            
+            //
+            // Compute 1-norm of each column of strictly upper triangular
+            // part of T to control overflow in triangular solver.
+            //
+            work[1] = 0;
+            for(j=2; j<=n; j++)
+            {
+                work[j] = 0;
+                for(i=1; i<=j-1; i++)
+                {
+                    work[j] = work[j]+Math.Abs(t[i,j]);
+                }
+            }
+            
+            //
+            // Index IP is used to specify the real or complex eigenvalue:
+            // IP = 0, real eigenvalue,
+            //      1, first of conjugate complex pair: (wr,wi)
+            //     -1, second of conjugate complex pair: (wr,wi)
+            //
+            n2 = 2*n;
+            if( rightv )
+            {
+                
+                //
+                // Compute right eigenvectors.
+                //
+                ip = 0;
+                iis = m;
+                for(ki=n; ki>=1; ki--)
+                {
+                    skipflag = false;
+                    if( ip==1 )
+                    {
+                        skipflag = true;
+                    }
+                    else
+                    {
+                        if( ki!=1 )
+                        {
+                            if( (double)(t[ki,ki-1])!=(double)(0) )
+                            {
+                                ip = -1;
+                            }
+                        }
+                        if( somev )
+                        {
+                            if( ip==0 )
+                            {
+                                if( !vselect[ki] )
+                                {
+                                    skipflag = true;
+                                }
+                            }
+                            else
+                            {
+                                if( !vselect[ki-1] )
+                                {
+                                    skipflag = true;
+                                }
+                            }
+                        }
+                    }
+                    if( !skipflag )
+                    {
+                        
+                        //
+                        // Compute the KI-th eigenvalue (WR,WI).
+                        //
+                        wr = t[ki,ki];
+                        wi = 0;
+                        if( ip!=0 )
+                        {
+                            wi = Math.Sqrt(Math.Abs(t[ki,ki-1]))*Math.Sqrt(Math.Abs(t[ki-1,ki]));
+                        }
+                        smin = Math.Max(ulp*(Math.Abs(wr)+Math.Abs(wi)), smlnum);
+                        if( ip==0 )
+                        {
+                            
+                            //
+                            // Real right eigenvector
+                            //
+                            work[ki+n] = 1;
+                            
+                            //
+                            // Form right-hand side
+                            //
+                            for(k=1; k<=ki-1; k++)
+                            {
+                                work[k+n] = -t[k,ki];
+                            }
+                            
+                            //
+                            // Solve the upper quasi-triangular system:
+                            //   (T(1:KI-1,1:KI-1) - WR)*X = SCALE*WORK.
+                            //
+                            jnxt = ki-1;
+                            for(j=ki-1; j>=1; j--)
+                            {
+                                if( j>jnxt )
+                                {
+                                    continue;
+                                }
+                                j1 = j;
+                                j2 = j;
+                                jnxt = j-1;
+                                if( j>1 )
+                                {
+                                    if( (double)(t[j,j-1])!=(double)(0) )
+                                    {
+                                        j1 = j-1;
+                                        jnxt = j-2;
+                                    }
+                                }
+                                if( j1==j2 )
+                                {
+                                    
+                                    //
+                                    // 1-by-1 diagonal block
+                                    //
+                                    temp11[1,1] = t[j,j];
+                                    temp11b[1,1] = work[j+n];
+                                    internalhsevdlaln2(false, 1, 1, smin, 1, temp11, 1.0, 1.0, temp11b, wr, 0.0, ref rswap4, ref zswap4, ref ipivot44, ref civ4, ref crv4, ref x, ref scl, ref xnorm, ref ierr);
+                                    
+                                    //
+                                    // Scale X(1,1) to avoid overflow when updating
+                                    // the right-hand side.
+                                    //
+                                    if( (double)(xnorm)>(double)(1) )
+                                    {
+                                        if( (double)(work[j])>(double)(bignum/xnorm) )
+                                        {
+                                            x[1,1] = x[1,1]/xnorm;
+                                            scl = scl/xnorm;
+                                        }
+                                    }
+                                    
+                                    //
+                                    // Scale if necessary
+                                    //
+                                    if( (double)(scl)!=(double)(1) )
+                                    {
+                                        k1 = n+1;
+                                        k2 = n+ki;
+                                        for(i_=k1; i_<=k2;i_++)
+                                        {
+                                            work[i_] = scl*work[i_];
+                                        }
+                                    }
+                                    work[j+n] = x[1,1];
+                                    
+                                    //
+                                    // Update right-hand side
+                                    //
+                                    k1 = 1+n;
+                                    k2 = j-1+n;
+                                    k3 = j-1;
+                                    vt = -x[1,1];
+                                    i1_ = (1) - (k1);
+                                    for(i_=k1; i_<=k2;i_++)
+                                    {
+                                        work[i_] = work[i_] + vt*t[i_+i1_,j];
+                                    }
+                                }
+                                else
+                                {
+                                    
+                                    //
+                                    // 2-by-2 diagonal block
+                                    //
+                                    temp22[1,1] = t[j-1,j-1];
+                                    temp22[1,2] = t[j-1,j];
+                                    temp22[2,1] = t[j,j-1];
+                                    temp22[2,2] = t[j,j];
+                                    temp21b[1,1] = work[j-1+n];
+                                    temp21b[2,1] = work[j+n];
+                                    internalhsevdlaln2(false, 2, 1, smin, 1.0, temp22, 1.0, 1.0, temp21b, wr, 0, ref rswap4, ref zswap4, ref ipivot44, ref civ4, ref crv4, ref x, ref scl, ref xnorm, ref ierr);
+                                    
+                                    //
+                                    // Scale X(1,1) and X(2,1) to avoid overflow when
+                                    // updating the right-hand side.
+                                    //
+                                    if( (double)(xnorm)>(double)(1) )
+                                    {
+                                        beta = Math.Max(work[j-1], work[j]);
+                                        if( (double)(beta)>(double)(bignum/xnorm) )
+                                        {
+                                            x[1,1] = x[1,1]/xnorm;
+                                            x[2,1] = x[2,1]/xnorm;
+                                            scl = scl/xnorm;
+                                        }
+                                    }
+                                    
+                                    //
+                                    // Scale if necessary
+                                    //
+                                    if( (double)(scl)!=(double)(1) )
+                                    {
+                                        k1 = 1+n;
+                                        k2 = ki+n;
+                                        for(i_=k1; i_<=k2;i_++)
+                                        {
+                                            work[i_] = scl*work[i_];
+                                        }
+                                    }
+                                    work[j-1+n] = x[1,1];
+                                    work[j+n] = x[2,1];
+                                    
+                                    //
+                                    // Update right-hand side
+                                    //
+                                    k1 = 1+n;
+                                    k2 = j-2+n;
+                                    k3 = j-2;
+                                    k4 = j-1;
+                                    vt = -x[1,1];
+                                    i1_ = (1) - (k1);
+                                    for(i_=k1; i_<=k2;i_++)
+                                    {
+                                        work[i_] = work[i_] + vt*t[i_+i1_,k4];
+                                    }
+                                    vt = -x[2,1];
+                                    i1_ = (1) - (k1);
+                                    for(i_=k1; i_<=k2;i_++)
+                                    {
+                                        work[i_] = work[i_] + vt*t[i_+i1_,j];
+                                    }
+                                }
+                            }
+                            
+                            //
+                            // Copy the vector x or Q*x to VR and normalize.
+                            //
+                            if( !over )
+                            {
+                                k1 = 1+n;
+                                k2 = ki+n;
+                                i1_ = (k1) - (1);
+                                for(i_=1; i_<=ki;i_++)
+                                {
+                                    vr[i_,iis] = work[i_+i1_];
+                                }
+                                ii = blas.columnidxabsmax(vr, 1, ki, iis);
+                                remax = 1/Math.Abs(vr[ii,iis]);
+                                for(i_=1; i_<=ki;i_++)
+                                {
+                                    vr[i_,iis] = remax*vr[i_,iis];
+                                }
+                                for(k=ki+1; k<=n; k++)
+                                {
+                                    vr[k,iis] = 0;
+                                }
+                            }
+                            else
+                            {
+                                if( ki>1 )
+                                {
+                                    for(i_=1; i_<=n;i_++)
+                                    {
+                                        temp[i_] = vr[i_,ki];
+                                    }
+                                    blas.matrixvectormultiply(vr, 1, n, 1, ki-1, false, work, 1+n, ki-1+n, 1.0, ref temp, 1, n, work[ki+n]);
+                                    for(i_=1; i_<=n;i_++)
+                                    {
+                                        vr[i_,ki] = temp[i_];
+                                    }
+                                }
+                                ii = blas.columnidxabsmax(vr, 1, n, ki);
+                                remax = 1/Math.Abs(vr[ii,ki]);
+                                for(i_=1; i_<=n;i_++)
+                                {
+                                    vr[i_,ki] = remax*vr[i_,ki];
+                                }
+                            }
+                        }
+                        else
+                        {
+                            
+                            //
+                            // Complex right eigenvector.
+                            //
+                            // Initial solve
+                            //     [ (T(KI-1,KI-1) T(KI-1,KI) ) - (WR + I* WI)]*X = 0.
+                            //     [ (T(KI,KI-1)   T(KI,KI)   )               ]
+                            //
+                            if( (double)(Math.Abs(t[ki-1,ki]))>=(double)(Math.Abs(t[ki,ki-1])) )
+                            {
+                                work[ki-1+n] = 1;
+                                work[ki+n2] = wi/t[ki-1,ki];
+                            }
+                            else
+                            {
+                                work[ki-1+n] = -(wi/t[ki,ki-1]);
+                                work[ki+n2] = 1;
+                            }
+                            work[ki+n] = 0;
+                            work[ki-1+n2] = 0;
+                            
+                            //
+                            // Form right-hand side
+                            //
+                            for(k=1; k<=ki-2; k++)
+                            {
+                                work[k+n] = -(work[ki-1+n]*t[k,ki-1]);
+                                work[k+n2] = -(work[ki+n2]*t[k,ki]);
+                            }
+                            
+                            //
+                            // Solve upper quasi-triangular system:
+                            // (T(1:KI-2,1:KI-2) - (WR+i*WI))*X = SCALE*(WORK+i*WORK2)
+                            //
+                            jnxt = ki-2;
+                            for(j=ki-2; j>=1; j--)
+                            {
+                                if( j>jnxt )
+                                {
+                                    continue;
+                                }
+                                j1 = j;
+                                j2 = j;
+                                jnxt = j-1;
+                                if( j>1 )
+                                {
+                                    if( (double)(t[j,j-1])!=(double)(0) )
+                                    {
+                                        j1 = j-1;
+                                        jnxt = j-2;
+                                    }
+                                }
+                                if( j1==j2 )
+                                {
+                                    
+                                    //
+                                    // 1-by-1 diagonal block
+                                    //
+                                    temp11[1,1] = t[j,j];
+                                    temp12b[1,1] = work[j+n];
+                                    temp12b[1,2] = work[j+n+n];
+                                    internalhsevdlaln2(false, 1, 2, smin, 1.0, temp11, 1.0, 1.0, temp12b, wr, wi, ref rswap4, ref zswap4, ref ipivot44, ref civ4, ref crv4, ref x, ref scl, ref xnorm, ref ierr);
+                                    
+                                    //
+                                    // Scale X(1,1) and X(1,2) to avoid overflow when
+                                    // updating the right-hand side.
+                                    //
+                                    if( (double)(xnorm)>(double)(1) )
+                                    {
+                                        if( (double)(work[j])>(double)(bignum/xnorm) )
+                                        {
+                                            x[1,1] = x[1,1]/xnorm;
+                                            x[1,2] = x[1,2]/xnorm;
+                                            scl = scl/xnorm;
+                                        }
+                                    }
+                                    
+                                    //
+                                    // Scale if necessary
+                                    //
+                                    if( (double)(scl)!=(double)(1) )
+                                    {
+                                        k1 = 1+n;
+                                        k2 = ki+n;
+                                        for(i_=k1; i_<=k2;i_++)
+                                        {
+                                            work[i_] = scl*work[i_];
+                                        }
+                                        k1 = 1+n2;
+                                        k2 = ki+n2;
+                                        for(i_=k1; i_<=k2;i_++)
+                                        {
+                                            work[i_] = scl*work[i_];
+                                        }
+                                    }
+                                    work[j+n] = x[1,1];
+                                    work[j+n2] = x[1,2];
+                                    
+                                    //
+                                    // Update the right-hand side
+                                    //
+                                    k1 = 1+n;
+                                    k2 = j-1+n;
+                                    k3 = 1;
+                                    k4 = j-1;
+                                    vt = -x[1,1];
+                                    i1_ = (k3) - (k1);
+                                    for(i_=k1; i_<=k2;i_++)
+                                    {
+                                        work[i_] = work[i_] + vt*t[i_+i1_,j];
+                                    }
+                                    k1 = 1+n2;
+                                    k2 = j-1+n2;
+                                    k3 = 1;
+                                    k4 = j-1;
+                                    vt = -x[1,2];
+                                    i1_ = (k3) - (k1);
+                                    for(i_=k1; i_<=k2;i_++)
+                                    {
+                                        work[i_] = work[i_] + vt*t[i_+i1_,j];
+                                    }
+                                }
+                                else
+                                {
+                                    
+                                    //
+                                    // 2-by-2 diagonal block
+                                    //
+                                    temp22[1,1] = t[j-1,j-1];
+                                    temp22[1,2] = t[j-1,j];
+                                    temp22[2,1] = t[j,j-1];
+                                    temp22[2,2] = t[j,j];
+                                    temp22b[1,1] = work[j-1+n];
+                                    temp22b[1,2] = work[j-1+n+n];
+                                    temp22b[2,1] = work[j+n];
+                                    temp22b[2,2] = work[j+n+n];
+                                    internalhsevdlaln2(false, 2, 2, smin, 1.0, temp22, 1.0, 1.0, temp22b, wr, wi, ref rswap4, ref zswap4, ref ipivot44, ref civ4, ref crv4, ref x, ref scl, ref xnorm, ref ierr);
+                                    
+                                    //
+                                    // Scale X to avoid overflow when updating
+                                    // the right-hand side.
+                                    //
+                                    if( (double)(xnorm)>(double)(1) )
+                                    {
+                                        beta = Math.Max(work[j-1], work[j]);
+                                        if( (double)(beta)>(double)(bignum/xnorm) )
+                                        {
+                                            rec = 1/xnorm;
+                                            x[1,1] = x[1,1]*rec;
+                                            x[1,2] = x[1,2]*rec;
+                                            x[2,1] = x[2,1]*rec;
+                                            x[2,2] = x[2,2]*rec;
+                                            scl = scl*rec;
+                                        }
+                                    }
+                                    
+                                    //
+                                    // Scale if necessary
+                                    //
+                                    if( (double)(scl)!=(double)(1) )
+                                    {
+                                        for(i_=1+n; i_<=ki+n;i_++)
+                                        {
+                                            work[i_] = scl*work[i_];
+                                        }
+                                        for(i_=1+n2; i_<=ki+n2;i_++)
+                                        {
+                                            work[i_] = scl*work[i_];
+                                        }
+                                    }
+                                    work[j-1+n] = x[1,1];
+                                    work[j+n] = x[2,1];
+                                    work[j-1+n2] = x[1,2];
+                                    work[j+n2] = x[2,2];
+                                    
+                                    //
+                                    // Update the right-hand side
+                                    //
+                                    vt = -x[1,1];
+                                    i1_ = (1) - (n+1);
+                                    for(i_=n+1; i_<=n+j-2;i_++)
+                                    {
+                                        work[i_] = work[i_] + vt*t[i_+i1_,j-1];
+                                    }
+                                    vt = -x[2,1];
+                                    i1_ = (1) - (n+1);
+                                    for(i_=n+1; i_<=n+j-2;i_++)
+                                    {
+                                        work[i_] = work[i_] + vt*t[i_+i1_,j];
+                                    }
+                                    vt = -x[1,2];
+                                    i1_ = (1) - (n2+1);
+                                    for(i_=n2+1; i_<=n2+j-2;i_++)
+                                    {
+                                        work[i_] = work[i_] + vt*t[i_+i1_,j-1];
+                                    }
+                                    vt = -x[2,2];
+                                    i1_ = (1) - (n2+1);
+                                    for(i_=n2+1; i_<=n2+j-2;i_++)
+                                    {
+                                        work[i_] = work[i_] + vt*t[i_+i1_,j];
+                                    }
+                                }
+                            }
+                            
+                            //
+                            // Copy the vector x or Q*x to VR and normalize.
+                            //
+                            if( !over )
+                            {
+                                i1_ = (n+1) - (1);
+                                for(i_=1; i_<=ki;i_++)
+                                {
+                                    vr[i_,iis-1] = work[i_+i1_];
+                                }
+                                i1_ = (n2+1) - (1);
+                                for(i_=1; i_<=ki;i_++)
+                                {
+                                    vr[i_,iis] = work[i_+i1_];
+                                }
+                                emax = 0;
+                                for(k=1; k<=ki; k++)
+                                {
+                                    emax = Math.Max(emax, Math.Abs(vr[k,iis-1])+Math.Abs(vr[k,iis]));
+                                }
+                                remax = 1/emax;
+                                for(i_=1; i_<=ki;i_++)
+                                {
+                                    vr[i_,iis-1] = remax*vr[i_,iis-1];
+                                }
+                                for(i_=1; i_<=ki;i_++)
+                                {
+                                    vr[i_,iis] = remax*vr[i_,iis];
+                                }
+                                for(k=ki+1; k<=n; k++)
+                                {
+                                    vr[k,iis-1] = 0;
+                                    vr[k,iis] = 0;
+                                }
+                            }
+                            else
+                            {
+                                if( ki>2 )
+                                {
+                                    for(i_=1; i_<=n;i_++)
+                                    {
+                                        temp[i_] = vr[i_,ki-1];
+                                    }
+                                    blas.matrixvectormultiply(vr, 1, n, 1, ki-2, false, work, 1+n, ki-2+n, 1.0, ref temp, 1, n, work[ki-1+n]);
+                                    for(i_=1; i_<=n;i_++)
+                                    {
+                                        vr[i_,ki-1] = temp[i_];
+                                    }
+                                    for(i_=1; i_<=n;i_++)
+                                    {
+                                        temp[i_] = vr[i_,ki];
+                                    }
+                                    blas.matrixvectormultiply(vr, 1, n, 1, ki-2, false, work, 1+n2, ki-2+n2, 1.0, ref temp, 1, n, work[ki+n2]);
+                                    for(i_=1; i_<=n;i_++)
+                                    {
+                                        vr[i_,ki] = temp[i_];
+                                    }
+                                }
+                                else
+                                {
+                                    vt = work[ki-1+n];
+                                    for(i_=1; i_<=n;i_++)
+                                    {
+                                        vr[i_,ki-1] = vt*vr[i_,ki-1];
+                                    }
+                                    vt = work[ki+n2];
+                                    for(i_=1; i_<=n;i_++)
+                                    {
+                                        vr[i_,ki] = vt*vr[i_,ki];
+                                    }
+                                }
+                                emax = 0;
+                                for(k=1; k<=n; k++)
+                                {
+                                    emax = Math.Max(emax, Math.Abs(vr[k,ki-1])+Math.Abs(vr[k,ki]));
+                                }
+                                remax = 1/emax;
+                                for(i_=1; i_<=n;i_++)
+                                {
+                                    vr[i_,ki-1] = remax*vr[i_,ki-1];
+                                }
+                                for(i_=1; i_<=n;i_++)
+                                {
+                                    vr[i_,ki] = remax*vr[i_,ki];
+                                }
+                            }
+                        }
+                        iis = iis-1;
+                        if( ip!=0 )
+                        {
+                            iis = iis-1;
+                        }
+                    }
+                    if( ip==1 )
+                    {
+                        ip = 0;
+                    }
+                    if( ip==-1 )
+                    {
+                        ip = 1;
+                    }
+                }
+            }
+            if( leftv )
+            {
+                
+                //
+                // Compute left eigenvectors.
+                //
+                ip = 0;
+                iis = 1;
+                for(ki=1; ki<=n; ki++)
+                {
+                    skipflag = false;
+                    if( ip==-1 )
+                    {
+                        skipflag = true;
+                    }
+                    else
+                    {
+                        if( ki!=n )
+                        {
+                            if( (double)(t[ki+1,ki])!=(double)(0) )
+                            {
+                                ip = 1;
+                            }
+                        }
+                        if( somev )
+                        {
+                            if( !vselect[ki] )
+                            {
+                                skipflag = true;
+                            }
+                        }
+                    }
+                    if( !skipflag )
+                    {
+                        
+                        //
+                        // Compute the KI-th eigenvalue (WR,WI).
+                        //
+                        wr = t[ki,ki];
+                        wi = 0;
+                        if( ip!=0 )
+                        {
+                            wi = Math.Sqrt(Math.Abs(t[ki,ki+1]))*Math.Sqrt(Math.Abs(t[ki+1,ki]));
+                        }
+                        smin = Math.Max(ulp*(Math.Abs(wr)+Math.Abs(wi)), smlnum);
+                        if( ip==0 )
+                        {
+                            
+                            //
+                            // Real left eigenvector.
+                            //
+                            work[ki+n] = 1;
+                            
+                            //
+                            // Form right-hand side
+                            //
+                            for(k=ki+1; k<=n; k++)
+                            {
+                                work[k+n] = -t[ki,k];
+                            }
+                            
+                            //
+                            // Solve the quasi-triangular system:
+                            // (T(KI+1:N,KI+1:N) - WR)'*X = SCALE*WORK
+                            //
+                            vmax = 1;
+                            vcrit = bignum;
+                            jnxt = ki+1;
+                            for(j=ki+1; j<=n; j++)
+                            {
+                                if( j<jnxt )
+                                {
+                                    continue;
+                                }
+                                j1 = j;
+                                j2 = j;
+                                jnxt = j+1;
+                                if( j<n )
+                                {
+                                    if( (double)(t[j+1,j])!=(double)(0) )
+                                    {
+                                        j2 = j+1;
+                                        jnxt = j+2;
+                                    }
+                                }
+                                if( j1==j2 )
+                                {
+                                    
+                                    //
+                                    // 1-by-1 diagonal block
+                                    //
+                                    // Scale if necessary to avoid overflow when forming
+                                    // the right-hand side.
+                                    //
+                                    if( (double)(work[j])>(double)(vcrit) )
+                                    {
+                                        rec = 1/vmax;
+                                        for(i_=ki+n; i_<=n+n;i_++)
+                                        {
+                                            work[i_] = rec*work[i_];
+                                        }
+                                        vmax = 1;
+                                        vcrit = bignum;
+                                    }
+                                    i1_ = (ki+1+n)-(ki+1);
+                                    vt = 0.0;
+                                    for(i_=ki+1; i_<=j-1;i_++)
+                                    {
+                                        vt += t[i_,j]*work[i_+i1_];
+                                    }
+                                    work[j+n] = work[j+n]-vt;
+                                    
+                                    //
+                                    // Solve (T(J,J)-WR)'*X = WORK
+                                    //
+                                    temp11[1,1] = t[j,j];
+                                    temp11b[1,1] = work[j+n];
+                                    internalhsevdlaln2(false, 1, 1, smin, 1.0, temp11, 1.0, 1.0, temp11b, wr, 0, ref rswap4, ref zswap4, ref ipivot44, ref civ4, ref crv4, ref x, ref scl, ref xnorm, ref ierr);
+                                    
+                                    //
+                                    // Scale if necessary
+                                    //
+                                    if( (double)(scl)!=(double)(1) )
+                                    {
+                                        for(i_=ki+n; i_<=n+n;i_++)
+                                        {
+                                            work[i_] = scl*work[i_];
+                                        }
+                                    }
+                                    work[j+n] = x[1,1];
+                                    vmax = Math.Max(Math.Abs(work[j+n]), vmax);
+                                    vcrit = bignum/vmax;
+                                }
+                                else
+                                {
+                                    
+                                    //
+                                    // 2-by-2 diagonal block
+                                    //
+                                    // Scale if necessary to avoid overflow when forming
+                                    // the right-hand side.
+                                    //
+                                    beta = Math.Max(work[j], work[j+1]);
+                                    if( (double)(beta)>(double)(vcrit) )
+                                    {
+                                        rec = 1/vmax;
+                                        for(i_=ki+n; i_<=n+n;i_++)
+                                        {
+                                            work[i_] = rec*work[i_];
+                                        }
+                                        vmax = 1;
+                                        vcrit = bignum;
+                                    }
+                                    i1_ = (ki+1+n)-(ki+1);
+                                    vt = 0.0;
+                                    for(i_=ki+1; i_<=j-1;i_++)
+                                    {
+                                        vt += t[i_,j]*work[i_+i1_];
+                                    }
+                                    work[j+n] = work[j+n]-vt;
+                                    i1_ = (ki+1+n)-(ki+1);
+                                    vt = 0.0;
+                                    for(i_=ki+1; i_<=j-1;i_++)
+                                    {
+                                        vt += t[i_,j+1]*work[i_+i1_];
+                                    }
+                                    work[j+1+n] = work[j+1+n]-vt;
+                                    
+                                    //
+                                    // Solve
+                                    //    [T(J,J)-WR   T(J,J+1)     ]'* X = SCALE*( WORK1 )
+                                    //    [T(J+1,J)    T(J+1,J+1)-WR]             ( WORK2 )
+                                    //
+                                    temp22[1,1] = t[j,j];
+                                    temp22[1,2] = t[j,j+1];
+                                    temp22[2,1] = t[j+1,j];
+                                    temp22[2,2] = t[j+1,j+1];
+                                    temp21b[1,1] = work[j+n];
+                                    temp21b[2,1] = work[j+1+n];
+                                    internalhsevdlaln2(true, 2, 1, smin, 1.0, temp22, 1.0, 1.0, temp21b, wr, 0, ref rswap4, ref zswap4, ref ipivot44, ref civ4, ref crv4, ref x, ref scl, ref xnorm, ref ierr);
+                                    
+                                    //
+                                    // Scale if necessary
+                                    //
+                                    if( (double)(scl)!=(double)(1) )
+                                    {
+                                        for(i_=ki+n; i_<=n+n;i_++)
+                                        {
+                                            work[i_] = scl*work[i_];
+                                        }
+                                    }
+                                    work[j+n] = x[1,1];
+                                    work[j+1+n] = x[2,1];
+                                    vmax = Math.Max(Math.Abs(work[j+n]), Math.Max(Math.Abs(work[j+1+n]), vmax));
+                                    vcrit = bignum/vmax;
+                                }
+                            }
+                            
+                            //
+                            // Copy the vector x or Q*x to VL and normalize.
+                            //
+                            if( !over )
+                            {
+                                i1_ = (ki+n) - (ki);
+                                for(i_=ki; i_<=n;i_++)
+                                {
+                                    vl[i_,iis] = work[i_+i1_];
+                                }
+                                ii = blas.columnidxabsmax(vl, ki, n, iis);
+                                remax = 1/Math.Abs(vl[ii,iis]);
+                                for(i_=ki; i_<=n;i_++)
+                                {
+                                    vl[i_,iis] = remax*vl[i_,iis];
+                                }
+                                for(k=1; k<=ki-1; k++)
+                                {
+                                    vl[k,iis] = 0;
+                                }
+                            }
+                            else
+                            {
+                                if( ki<n )
+                                {
+                                    for(i_=1; i_<=n;i_++)
+                                    {
+                                        temp[i_] = vl[i_,ki];
+                                    }
+                                    blas.matrixvectormultiply(vl, 1, n, ki+1, n, false, work, ki+1+n, n+n, 1.0, ref temp, 1, n, work[ki+n]);
+                                    for(i_=1; i_<=n;i_++)
+                                    {
+                                        vl[i_,ki] = temp[i_];
+                                    }
+                                }
+                                ii = blas.columnidxabsmax(vl, 1, n, ki);
+                                remax = 1/Math.Abs(vl[ii,ki]);
+                                for(i_=1; i_<=n;i_++)
+                                {
+                                    vl[i_,ki] = remax*vl[i_,ki];
+                                }
+                            }
+                        }
+                        else
+                        {
+                            
+                            //
+                            // Complex left eigenvector.
+                            //
+                            // Initial solve:
+                            //   ((T(KI,KI)    T(KI,KI+1) )' - (WR - I* WI))*X = 0.
+                            //   ((T(KI+1,KI) T(KI+1,KI+1))                )
+                            //
+                            if( (double)(Math.Abs(t[ki,ki+1]))>=(double)(Math.Abs(t[ki+1,ki])) )
+                            {
+                                work[ki+n] = wi/t[ki,ki+1];
+                                work[ki+1+n2] = 1;
+                            }
+                            else
+                            {
+                                work[ki+n] = 1;
+                                work[ki+1+n2] = -(wi/t[ki+1,ki]);
+                            }
+                            work[ki+1+n] = 0;
+                            work[ki+n2] = 0;
+                            
+                            //
+                            // Form right-hand side
+                            //
+                            for(k=ki+2; k<=n; k++)
+                            {
+                                work[k+n] = -(work[ki+n]*t[ki,k]);
+                                work[k+n2] = -(work[ki+1+n2]*t[ki+1,k]);
+                            }
+                            
+                            //
+                            // Solve complex quasi-triangular system:
+                            // ( T(KI+2,N:KI+2,N) - (WR-i*WI) )*X = WORK1+i*WORK2
+                            //
+                            vmax = 1;
+                            vcrit = bignum;
+                            jnxt = ki+2;
+                            for(j=ki+2; j<=n; j++)
+                            {
+                                if( j<jnxt )
+                                {
+                                    continue;
+                                }
+                                j1 = j;
+                                j2 = j;
+                                jnxt = j+1;
+                                if( j<n )
+                                {
+                                    if( (double)(t[j+1,j])!=(double)(0) )
+                                    {
+                                        j2 = j+1;
+                                        jnxt = j+2;
+                                    }
+                                }
+                                if( j1==j2 )
+                                {
+                                    
+                                    //
+                                    // 1-by-1 diagonal block
+                                    //
+                                    // Scale if necessary to avoid overflow when
+                                    // forming the right-hand side elements.
+                                    //
+                                    if( (double)(work[j])>(double)(vcrit) )
+                                    {
+                                        rec = 1/vmax;
+                                        for(i_=ki+n; i_<=n+n;i_++)
+                                        {
+                                            work[i_] = rec*work[i_];
+                                        }
+                                        for(i_=ki+n2; i_<=n+n2;i_++)
+                                        {
+                                            work[i_] = rec*work[i_];
+                                        }
+                                        vmax = 1;
+                                        vcrit = bignum;
+                                    }
+                                    i1_ = (ki+2+n)-(ki+2);
+                                    vt = 0.0;
+                                    for(i_=ki+2; i_<=j-1;i_++)
+                                    {
+                                        vt += t[i_,j]*work[i_+i1_];
+                                    }
+                                    work[j+n] = work[j+n]-vt;
+                                    i1_ = (ki+2+n2)-(ki+2);
+                                    vt = 0.0;
+                                    for(i_=ki+2; i_<=j-1;i_++)
+                                    {
+                                        vt += t[i_,j]*work[i_+i1_];
+                                    }
+                                    work[j+n2] = work[j+n2]-vt;
+                                    
+                                    //
+                                    // Solve (T(J,J)-(WR-i*WI))*(X11+i*X12)= WK+I*WK2
+                                    //
+                                    temp11[1,1] = t[j,j];
+                                    temp12b[1,1] = work[j+n];
+                                    temp12b[1,2] = work[j+n+n];
+                                    internalhsevdlaln2(false, 1, 2, smin, 1.0, temp11, 1.0, 1.0, temp12b, wr, -wi, ref rswap4, ref zswap4, ref ipivot44, ref civ4, ref crv4, ref x, ref scl, ref xnorm, ref ierr);
+                                    
+                                    //
+                                    // Scale if necessary
+                                    //
+                                    if( (double)(scl)!=(double)(1) )
+                                    {
+                                        for(i_=ki+n; i_<=n+n;i_++)
+                                        {
+                                            work[i_] = scl*work[i_];
+                                        }
+                                        for(i_=ki+n2; i_<=n+n2;i_++)
+                                        {
+                                            work[i_] = scl*work[i_];
+                                        }
+                                    }
+                                    work[j+n] = x[1,1];
+                                    work[j+n2] = x[1,2];
+                                    vmax = Math.Max(Math.Abs(work[j+n]), Math.Max(Math.Abs(work[j+n2]), vmax));
+                                    vcrit = bignum/vmax;
+                                }
+                                else
+                                {
+                                    
+                                    //
+                                    // 2-by-2 diagonal block
+                                    //
+                                    // Scale if necessary to avoid overflow when forming
+                                    // the right-hand side elements.
+                                    //
+                                    beta = Math.Max(work[j], work[j+1]);
+                                    if( (double)(beta)>(double)(vcrit) )
+                                    {
+                                        rec = 1/vmax;
+                                        for(i_=ki+n; i_<=n+n;i_++)
+                                        {
+                                            work[i_] = rec*work[i_];
+                                        }
+                                        for(i_=ki+n2; i_<=n+n2;i_++)
+                                        {
+                                            work[i_] = rec*work[i_];
+                                        }
+                                        vmax = 1;
+                                        vcrit = bignum;
+                                    }
+                                    i1_ = (ki+2+n)-(ki+2);
+                                    vt = 0.0;
+                                    for(i_=ki+2; i_<=j-1;i_++)
+                                    {
+                                        vt += t[i_,j]*work[i_+i1_];
+                                    }
+                                    work[j+n] = work[j+n]-vt;
+                                    i1_ = (ki+2+n2)-(ki+2);
+                                    vt = 0.0;
+                                    for(i_=ki+2; i_<=j-1;i_++)
+                                    {
+                                        vt += t[i_,j]*work[i_+i1_];
+                                    }
+                                    work[j+n2] = work[j+n2]-vt;
+                                    i1_ = (ki+2+n)-(ki+2);
+                                    vt = 0.0;
+                                    for(i_=ki+2; i_<=j-1;i_++)
+                                    {
+                                        vt += t[i_,j+1]*work[i_+i1_];
+                                    }
+                                    work[j+1+n] = work[j+1+n]-vt;
+                                    i1_ = (ki+2+n2)-(ki+2);
+                                    vt = 0.0;
+                                    for(i_=ki+2; i_<=j-1;i_++)
+                                    {
+                                        vt += t[i_,j+1]*work[i_+i1_];
+                                    }
+                                    work[j+1+n2] = work[j+1+n2]-vt;
+                                    
+                                    //
+                                    // Solve 2-by-2 complex linear equation
+                                    //   ([T(j,j)   T(j,j+1)  ]'-(wr-i*wi)*I)*X = SCALE*B
+                                    //   ([T(j+1,j) T(j+1,j+1)]             )
+                                    //
+                                    temp22[1,1] = t[j,j];
+                                    temp22[1,2] = t[j,j+1];
+                                    temp22[2,1] = t[j+1,j];
+                                    temp22[2,2] = t[j+1,j+1];
+                                    temp22b[1,1] = work[j+n];
+                                    temp22b[1,2] = work[j+n+n];
+                                    temp22b[2,1] = work[j+1+n];
+                                    temp22b[2,2] = work[j+1+n+n];
+                                    internalhsevdlaln2(true, 2, 2, smin, 1.0, temp22, 1.0, 1.0, temp22b, wr, -wi, ref rswap4, ref zswap4, ref ipivot44, ref civ4, ref crv4, ref x, ref scl, ref xnorm, ref ierr);
+                                    
+                                    //
+                                    // Scale if necessary
+                                    //
+                                    if( (double)(scl)!=(double)(1) )
+                                    {
+                                        for(i_=ki+n; i_<=n+n;i_++)
+                                        {
+                                            work[i_] = scl*work[i_];
+                                        }
+                                        for(i_=ki+n2; i_<=n+n2;i_++)
+                                        {
+                                            work[i_] = scl*work[i_];
+                                        }
+                                    }
+                                    work[j+n] = x[1,1];
+                                    work[j+n2] = x[1,2];
+                                    work[j+1+n] = x[2,1];
+                                    work[j+1+n2] = x[2,2];
+                                    vmax = Math.Max(Math.Abs(x[1,1]), vmax);
+                                    vmax = Math.Max(Math.Abs(x[1,2]), vmax);
+                                    vmax = Math.Max(Math.Abs(x[2,1]), vmax);
+                                    vmax = Math.Max(Math.Abs(x[2,2]), vmax);
+                                    vcrit = bignum/vmax;
+                                }
+                            }
+                            
+                            //
+                            // Copy the vector x or Q*x to VL and normalize.
+                            //
+                            if( !over )
+                            {
+                                i1_ = (ki+n) - (ki);
+                                for(i_=ki; i_<=n;i_++)
+                                {
+                                    vl[i_,iis] = work[i_+i1_];
+                                }
+                                i1_ = (ki+n2) - (ki);
+                                for(i_=ki; i_<=n;i_++)
+                                {
+                                    vl[i_,iis+1] = work[i_+i1_];
+                                }
+                                emax = 0;
+                                for(k=ki; k<=n; k++)
+                                {
+                                    emax = Math.Max(emax, Math.Abs(vl[k,iis])+Math.Abs(vl[k,iis+1]));
+                                }
+                                remax = 1/emax;
+                                for(i_=ki; i_<=n;i_++)
+                                {
+                                    vl[i_,iis] = remax*vl[i_,iis];
+                                }
+                                for(i_=ki; i_<=n;i_++)
+                                {
+                                    vl[i_,iis+1] = remax*vl[i_,iis+1];
+                                }
+                                for(k=1; k<=ki-1; k++)
+                                {
+                                    vl[k,iis] = 0;
+                                    vl[k,iis+1] = 0;
+                                }
+                            }
+                            else
+                            {
+                                if( ki<n-1 )
+                                {
+                                    for(i_=1; i_<=n;i_++)
+                                    {
+                                        temp[i_] = vl[i_,ki];
+                                    }
+                                    blas.matrixvectormultiply(vl, 1, n, ki+2, n, false, work, ki+2+n, n+n, 1.0, ref temp, 1, n, work[ki+n]);
+                                    for(i_=1; i_<=n;i_++)
+                                    {
+                                        vl[i_,ki] = temp[i_];
+                                    }
+                                    for(i_=1; i_<=n;i_++)
+                                    {
+                                        temp[i_] = vl[i_,ki+1];
+                                    }
+                                    blas.matrixvectormultiply(vl, 1, n, ki+2, n, false, work, ki+2+n2, n+n2, 1.0, ref temp, 1, n, work[ki+1+n2]);
+                                    for(i_=1; i_<=n;i_++)
+                                    {
+                                        vl[i_,ki+1] = temp[i_];
+                                    }
+                                }
+                                else
+                                {
+                                    vt = work[ki+n];
+                                    for(i_=1; i_<=n;i_++)
+                                    {
+                                        vl[i_,ki] = vt*vl[i_,ki];
+                                    }
+                                    vt = work[ki+1+n2];
+                                    for(i_=1; i_<=n;i_++)
+                                    {
+                                        vl[i_,ki+1] = vt*vl[i_,ki+1];
+                                    }
+                                }
+                                emax = 0;
+                                for(k=1; k<=n; k++)
+                                {
+                                    emax = Math.Max(emax, Math.Abs(vl[k,ki])+Math.Abs(vl[k,ki+1]));
+                                }
+                                remax = 1/emax;
+                                for(i_=1; i_<=n;i_++)
+                                {
+                                    vl[i_,ki] = remax*vl[i_,ki];
+                                }
+                                for(i_=1; i_<=n;i_++)
+                                {
+                                    vl[i_,ki+1] = remax*vl[i_,ki+1];
+                                }
+                            }
+                        }
+                        iis = iis+1;
+                        if( ip!=0 )
+                        {
+                            iis = iis+1;
+                        }
+                    }
+                    if( ip==-1 )
+                    {
+                        ip = 0;
+                    }
+                    if( ip==1 )
+                    {
+                        ip = -1;
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        DLALN2 solves a system of the form  (ca A - w D ) X = s B
+        or (ca A' - w D) X = s B   with possible scaling ("s") and
+        perturbation of A.  (A' means A-transpose.)
+
+        A is an NA x NA real matrix, ca is a real scalar, D is an NA x NA
+        real diagonal matrix, w is a real or complex value, and X and B are
+        NA x 1 matrices -- real if w is real, complex if w is complex.  NA
+        may be 1 or 2.
+
+        If w is complex, X and B are represented as NA x 2 matrices,
+        the first column of each being the real part and the second
+        being the imaginary part.
+
+        "s" is a scaling factor (.LE. 1), computed by DLALN2, which is
+        so chosen that X can be computed without overflow.  X is further
+        scaled if necessary to assure that norm(ca A - w D)*norm(X) is less
+        than overflow.
+
+        If both singular values of (ca A - w D) are less than SMIN,
+        SMIN*identity will be used instead of (ca A - w D).  If only one
+        singular value is less than SMIN, one element of (ca A - w D) will be
+        perturbed enough to make the smallest singular value roughly SMIN.
+        If both singular values are at least SMIN, (ca A - w D) will not be
+        perturbed.  In any case, the perturbation will be at most some small
+        multiple of max( SMIN, ulp*norm(ca A - w D) ).  The singular values
+        are computed by infinity-norm approximations, and thus will only be
+        correct to a factor of 2 or so.
+
+        Note: all input quantities are assumed to be smaller than overflow
+        by a reasonable factor.  (See BIGNUM.)
+
+          -- LAPACK auxiliary routine (version 3.0) --
+             Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd.,
+             Courant Institute, Argonne National Lab, and Rice University
+             October 31, 1992
+        *************************************************************************/
+        private static void internalhsevdlaln2(bool ltrans,
+            int na,
+            int nw,
+            double smin,
+            double ca,
+            double[,] a,
+            double d1,
+            double d2,
+            double[,] b,
+            double wr,
+            double wi,
+            ref bool[] rswap4,
+            ref bool[] zswap4,
+            ref int[,] ipivot44,
+            ref double[] civ4,
+            ref double[] crv4,
+            ref double[,] x,
+            ref double scl,
+            ref double xnorm,
+            ref int info)
+        {
+            int icmax = 0;
+            int j = 0;
+            double bbnd = 0;
+            double bi1 = 0;
+            double bi2 = 0;
+            double bignum = 0;
+            double bnorm = 0;
+            double br1 = 0;
+            double br2 = 0;
+            double ci21 = 0;
+            double ci22 = 0;
+            double cmax = 0;
+            double cnorm = 0;
+            double cr21 = 0;
+            double cr22 = 0;
+            double csi = 0;
+            double csr = 0;
+            double li21 = 0;
+            double lr21 = 0;
+            double smini = 0;
+            double smlnum = 0;
+            double temp = 0;
+            double u22abs = 0;
+            double ui11 = 0;
+            double ui11r = 0;
+            double ui12 = 0;
+            double ui12s = 0;
+            double ui22 = 0;
+            double ur11 = 0;
+            double ur11r = 0;
+            double ur12 = 0;
+            double ur12s = 0;
+            double ur22 = 0;
+            double xi1 = 0;
+            double xi2 = 0;
+            double xr1 = 0;
+            double xr2 = 0;
+            double tmp1 = 0;
+            double tmp2 = 0;
+
+            scl = 0;
+            xnorm = 0;
+            info = 0;
+
+            zswap4[1] = false;
+            zswap4[2] = false;
+            zswap4[3] = true;
+            zswap4[4] = true;
+            rswap4[1] = false;
+            rswap4[2] = true;
+            rswap4[3] = false;
+            rswap4[4] = true;
+            ipivot44[1,1] = 1;
+            ipivot44[2,1] = 2;
+            ipivot44[3,1] = 3;
+            ipivot44[4,1] = 4;
+            ipivot44[1,2] = 2;
+            ipivot44[2,2] = 1;
+            ipivot44[3,2] = 4;
+            ipivot44[4,2] = 3;
+            ipivot44[1,3] = 3;
+            ipivot44[2,3] = 4;
+            ipivot44[3,3] = 1;
+            ipivot44[4,3] = 2;
+            ipivot44[1,4] = 4;
+            ipivot44[2,4] = 3;
+            ipivot44[3,4] = 2;
+            ipivot44[4,4] = 1;
+            smlnum = 2*math.minrealnumber;
+            bignum = 1/smlnum;
+            smini = Math.Max(smin, smlnum);
+            
+            //
+            // Don't check for input errors
+            //
+            info = 0;
+            
+            //
+            // Standard Initializations
+            //
+            scl = 1;
+            if( na==1 )
+            {
+                
+                //
+                // 1 x 1  (i.e., scalar) system   C X = B
+                //
+                if( nw==1 )
+                {
+                    
+                    //
+                    // Real 1x1 system.
+                    //
+                    // C = ca A - w D
+                    //
+                    csr = ca*a[1,1]-wr*d1;
+                    cnorm = Math.Abs(csr);
+                    
+                    //
+                    // If | C | < SMINI, use C = SMINI
+                    //
+                    if( (double)(cnorm)<(double)(smini) )
+                    {
+                        csr = smini;
+                        cnorm = smini;
+                        info = 1;
+                    }
+                    
+                    //
+                    // Check scaling for  X = B / C
+                    //
+                    bnorm = Math.Abs(b[1,1]);
+                    if( (double)(cnorm)<(double)(1) && (double)(bnorm)>(double)(1) )
+                    {
+                        if( (double)(bnorm)>(double)(bignum*cnorm) )
+                        {
+                            scl = 1/bnorm;
+                        }
+                    }
+                    
+                    //
+                    // Compute X
+                    //
+                    x[1,1] = b[1,1]*scl/csr;
+                    xnorm = Math.Abs(x[1,1]);
+                }
+                else
+                {
+                    
+                    //
+                    // Complex 1x1 system (w is complex)
+                    //
+                    // C = ca A - w D
+                    //
+                    csr = ca*a[1,1]-wr*d1;
+                    csi = -(wi*d1);
+                    cnorm = Math.Abs(csr)+Math.Abs(csi);
+                    
+                    //
+                    // If | C | < SMINI, use C = SMINI
+                    //
+                    if( (double)(cnorm)<(double)(smini) )
+                    {
+                        csr = smini;
+                        csi = 0;
+                        cnorm = smini;
+                        info = 1;
+                    }
+                    
+                    //
+                    // Check scaling for  X = B / C
+                    //
+                    bnorm = Math.Abs(b[1,1])+Math.Abs(b[1,2]);
+                    if( (double)(cnorm)<(double)(1) && (double)(bnorm)>(double)(1) )
+                    {
+                        if( (double)(bnorm)>(double)(bignum*cnorm) )
+                        {
+                            scl = 1/bnorm;
+                        }
+                    }
+                    
+                    //
+                    // Compute X
+                    //
+                    internalhsevdladiv(scl*b[1,1], scl*b[1,2], csr, csi, ref tmp1, ref tmp2);
+                    x[1,1] = tmp1;
+                    x[1,2] = tmp2;
+                    xnorm = Math.Abs(x[1,1])+Math.Abs(x[1,2]);
+                }
+            }
+            else
+            {
+                
+                //
+                // 2x2 System
+                //
+                // Compute the real part of  C = ca A - w D  (or  ca A' - w D )
+                //
+                crv4[1+0] = ca*a[1,1]-wr*d1;
+                crv4[2+2] = ca*a[2,2]-wr*d2;
+                if( ltrans )
+                {
+                    crv4[1+2] = ca*a[2,1];
+                    crv4[2+0] = ca*a[1,2];
+                }
+                else
+                {
+                    crv4[2+0] = ca*a[2,1];
+                    crv4[1+2] = ca*a[1,2];
+                }
+                if( nw==1 )
+                {
+                    
+                    //
+                    // Real 2x2 system  (w is real)
+                    //
+                    // Find the largest element in C
+                    //
+                    cmax = 0;
+                    icmax = 0;
+                    for(j=1; j<=4; j++)
+                    {
+                        if( (double)(Math.Abs(crv4[j]))>(double)(cmax) )
+                        {
+                            cmax = Math.Abs(crv4[j]);
+                            icmax = j;
+                        }
+                    }
+                    
+                    //
+                    // If norm(C) < SMINI, use SMINI*identity.
+                    //
+                    if( (double)(cmax)<(double)(smini) )
+                    {
+                        bnorm = Math.Max(Math.Abs(b[1,1]), Math.Abs(b[2,1]));
+                        if( (double)(smini)<(double)(1) && (double)(bnorm)>(double)(1) )
+                        {
+                            if( (double)(bnorm)>(double)(bignum*smini) )
+                            {
+                                scl = 1/bnorm;
+                            }
+                        }
+                        temp = scl/smini;
+                        x[1,1] = temp*b[1,1];
+                        x[2,1] = temp*b[2,1];
+                        xnorm = temp*bnorm;
+                        info = 1;
+                        return;
+                    }
+                    
+                    //
+                    // Gaussian elimination with complete pivoting.
+                    //
+                    ur11 = crv4[icmax];
+                    cr21 = crv4[ipivot44[2,icmax]];
+                    ur12 = crv4[ipivot44[3,icmax]];
+                    cr22 = crv4[ipivot44[4,icmax]];
+                    ur11r = 1/ur11;
+                    lr21 = ur11r*cr21;
+                    ur22 = cr22-ur12*lr21;
+                    
+                    //
+                    // If smaller pivot < SMINI, use SMINI
+                    //
+                    if( (double)(Math.Abs(ur22))<(double)(smini) )
+                    {
+                        ur22 = smini;
+                        info = 1;
+                    }
+                    if( rswap4[icmax] )
+                    {
+                        br1 = b[2,1];
+                        br2 = b[1,1];
+                    }
+                    else
+                    {
+                        br1 = b[1,1];
+                        br2 = b[2,1];
+                    }
+                    br2 = br2-lr21*br1;
+                    bbnd = Math.Max(Math.Abs(br1*(ur22*ur11r)), Math.Abs(br2));
+                    if( (double)(bbnd)>(double)(1) && (double)(Math.Abs(ur22))<(double)(1) )
+                    {
+                        if( (double)(bbnd)>=(double)(bignum*Math.Abs(ur22)) )
+                        {
+                            scl = 1/bbnd;
+                        }
+                    }
+                    xr2 = br2*scl/ur22;
+                    xr1 = scl*br1*ur11r-xr2*(ur11r*ur12);
+                    if( zswap4[icmax] )
+                    {
+                        x[1,1] = xr2;
+                        x[2,1] = xr1;
+                    }
+                    else
+                    {
+                        x[1,1] = xr1;
+                        x[2,1] = xr2;
+                    }
+                    xnorm = Math.Max(Math.Abs(xr1), Math.Abs(xr2));
+                    
+                    //
+                    // Further scaling if  norm(A) norm(X) > overflow
+                    //
+                    if( (double)(xnorm)>(double)(1) && (double)(cmax)>(double)(1) )
+                    {
+                        if( (double)(xnorm)>(double)(bignum/cmax) )
+                        {
+                            temp = cmax/bignum;
+                            x[1,1] = temp*x[1,1];
+                            x[2,1] = temp*x[2,1];
+                            xnorm = temp*xnorm;
+                            scl = temp*scl;
+                        }
+                    }
+                }
+                else
+                {
+                    
+                    //
+                    // Complex 2x2 system  (w is complex)
+                    //
+                    // Find the largest element in C
+                    //
+                    civ4[1+0] = -(wi*d1);
+                    civ4[2+0] = 0;
+                    civ4[1+2] = 0;
+                    civ4[2+2] = -(wi*d2);
+                    cmax = 0;
+                    icmax = 0;
+                    for(j=1; j<=4; j++)
+                    {
+                        if( (double)(Math.Abs(crv4[j])+Math.Abs(civ4[j]))>(double)(cmax) )
+                        {
+                            cmax = Math.Abs(crv4[j])+Math.Abs(civ4[j]);
+                            icmax = j;
+                        }
+                    }
+                    
+                    //
+                    // If norm(C) < SMINI, use SMINI*identity.
+                    //
+                    if( (double)(cmax)<(double)(smini) )
+                    {
+                        bnorm = Math.Max(Math.Abs(b[1,1])+Math.Abs(b[1,2]), Math.Abs(b[2,1])+Math.Abs(b[2,2]));
+                        if( (double)(smini)<(double)(1) && (double)(bnorm)>(double)(1) )
+                        {
+                            if( (double)(bnorm)>(double)(bignum*smini) )
+                            {
+                                scl = 1/bnorm;
+                            }
+                        }
+                        temp = scl/smini;
+                        x[1,1] = temp*b[1,1];
+                        x[2,1] = temp*b[2,1];
+                        x[1,2] = temp*b[1,2];
+                        x[2,2] = temp*b[2,2];
+                        xnorm = temp*bnorm;
+                        info = 1;
+                        return;
+                    }
+                    
+                    //
+                    // Gaussian elimination with complete pivoting.
+                    //
+                    ur11 = crv4[icmax];
+                    ui11 = civ4[icmax];
+                    cr21 = crv4[ipivot44[2,icmax]];
+                    ci21 = civ4[ipivot44[2,icmax]];
+                    ur12 = crv4[ipivot44[3,icmax]];
+                    ui12 = civ4[ipivot44[3,icmax]];
+                    cr22 = crv4[ipivot44[4,icmax]];
+                    ci22 = civ4[ipivot44[4,icmax]];
+                    if( icmax==1 || icmax==4 )
+                    {
+                        
+                        //
+                        // Code when off-diagonals of pivoted C are real
+                        //
+                        if( (double)(Math.Abs(ur11))>(double)(Math.Abs(ui11)) )
+                        {
+                            temp = ui11/ur11;
+                            ur11r = 1/(ur11*(1+math.sqr(temp)));
+                            ui11r = -(temp*ur11r);
+                        }
+                        else
+                        {
+                            temp = ur11/ui11;
+                            ui11r = -(1/(ui11*(1+math.sqr(temp))));
+                            ur11r = -(temp*ui11r);
+                        }
+                        lr21 = cr21*ur11r;
+                        li21 = cr21*ui11r;
+                        ur12s = ur12*ur11r;
+                        ui12s = ur12*ui11r;
+                        ur22 = cr22-ur12*lr21;
+                        ui22 = ci22-ur12*li21;
+                    }
+                    else
+                    {
+                        
+                        //
+                        // Code when diagonals of pivoted C are real
+                        //
+                        ur11r = 1/ur11;
+                        ui11r = 0;
+                        lr21 = cr21*ur11r;
+                        li21 = ci21*ur11r;
+                        ur12s = ur12*ur11r;
+                        ui12s = ui12*ur11r;
+                        ur22 = cr22-ur12*lr21+ui12*li21;
+                        ui22 = -(ur12*li21)-ui12*lr21;
+                    }
+                    u22abs = Math.Abs(ur22)+Math.Abs(ui22);
+                    
+                    //
+                    // If smaller pivot < SMINI, use SMINI
+                    //
+                    if( (double)(u22abs)<(double)(smini) )
+                    {
+                        ur22 = smini;
+                        ui22 = 0;
+                        info = 1;
+                    }
+                    if( rswap4[icmax] )
+                    {
+                        br2 = b[1,1];
+                        br1 = b[2,1];
+                        bi2 = b[1,2];
+                        bi1 = b[2,2];
+                    }
+                    else
+                    {
+                        br1 = b[1,1];
+                        br2 = b[2,1];
+                        bi1 = b[1,2];
+                        bi2 = b[2,2];
+                    }
+                    br2 = br2-lr21*br1+li21*bi1;
+                    bi2 = bi2-li21*br1-lr21*bi1;
+                    bbnd = Math.Max((Math.Abs(br1)+Math.Abs(bi1))*(u22abs*(Math.Abs(ur11r)+Math.Abs(ui11r))), Math.Abs(br2)+Math.Abs(bi2));
+                    if( (double)(bbnd)>(double)(1) && (double)(u22abs)<(double)(1) )
+                    {
+                        if( (double)(bbnd)>=(double)(bignum*u22abs) )
+                        {
+                            scl = 1/bbnd;
+                            br1 = scl*br1;
+                            bi1 = scl*bi1;
+                            br2 = scl*br2;
+                            bi2 = scl*bi2;
+                        }
+                    }
+                    internalhsevdladiv(br2, bi2, ur22, ui22, ref xr2, ref xi2);
+                    xr1 = ur11r*br1-ui11r*bi1-ur12s*xr2+ui12s*xi2;
+                    xi1 = ui11r*br1+ur11r*bi1-ui12s*xr2-ur12s*xi2;
+                    if( zswap4[icmax] )
+                    {
+                        x[1,1] = xr2;
+                        x[2,1] = xr1;
+                        x[1,2] = xi2;
+                        x[2,2] = xi1;
+                    }
+                    else
+                    {
+                        x[1,1] = xr1;
+                        x[2,1] = xr2;
+                        x[1,2] = xi1;
+                        x[2,2] = xi2;
+                    }
+                    xnorm = Math.Max(Math.Abs(xr1)+Math.Abs(xi1), Math.Abs(xr2)+Math.Abs(xi2));
+                    
+                    //
+                    // Further scaling if  norm(A) norm(X) > overflow
+                    //
+                    if( (double)(xnorm)>(double)(1) && (double)(cmax)>(double)(1) )
+                    {
+                        if( (double)(xnorm)>(double)(bignum/cmax) )
+                        {
+                            temp = cmax/bignum;
+                            x[1,1] = temp*x[1,1];
+                            x[2,1] = temp*x[2,1];
+                            x[1,2] = temp*x[1,2];
+                            x[2,2] = temp*x[2,2];
+                            xnorm = temp*xnorm;
+                            scl = temp*scl;
+                        }
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        performs complex division in  real arithmetic
+
+                                a + i*b
+                     p + i*q = ---------
+                                c + i*d
+
+        The algorithm is due to Robert L. Smith and can be found
+        in D. Knuth, The art of Computer Programming, Vol.2, p.195
+
+          -- LAPACK auxiliary routine (version 3.0) --
+             Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd.,
+             Courant Institute, Argonne National Lab, and Rice University
+             October 31, 1992
+        *************************************************************************/
+        private static void internalhsevdladiv(double a,
+            double b,
+            double c,
+            double d,
+            ref double p,
+            ref double q)
+        {
+            double e = 0;
+            double f = 0;
+
+            p = 0;
+            q = 0;
+
+            if( (double)(Math.Abs(d))<(double)(Math.Abs(c)) )
+            {
+                e = d/c;
+                f = c+d*e;
+                p = (a+b*e)/f;
+                q = (b-a*e)/f;
+            }
+            else
+            {
+                e = c/d;
+                f = d+c*e;
+                p = (b+a*e)/f;
+                q = (-a+b*e)/f;
+            }
+        }
+
+
+        private static bool nonsymmetricevd(double[,] a,
+            int n,
+            int vneeded,
+            ref double[] wr,
+            ref double[] wi,
+            ref double[,] vl,
+            ref double[,] vr)
+        {
+            bool result = new bool();
+            double[,] s = new double[0,0];
+            double[] tau = new double[0];
+            bool[] sel = new bool[0];
+            int i = 0;
+            int info = 0;
+            int m = 0;
+            int i_ = 0;
+
+            a = (double[,])a.Clone();
+            wr = new double[0];
+            wi = new double[0];
+            vl = new double[0,0];
+            vr = new double[0,0];
+
+            alglib.ap.assert(vneeded>=0 && vneeded<=3, "NonSymmetricEVD: incorrect VNeeded!");
+            if( vneeded==0 )
+            {
+                
+                //
+                // Eigen values only
+                //
+                toupperhessenberg(ref a, n, ref tau);
+                hsschur.internalschurdecomposition(ref a, n, 0, 0, ref wr, ref wi, ref s, ref info);
+                result = info==0;
+                return result;
+            }
+            
+            //
+            // Eigen values and vectors
+            //
+            toupperhessenberg(ref a, n, ref tau);
+            unpackqfromupperhessenberg(a, n, tau, ref s);
+            hsschur.internalschurdecomposition(ref a, n, 1, 1, ref wr, ref wi, ref s, ref info);
+            result = info==0;
+            if( !result )
+            {
+                return result;
+            }
+            if( vneeded==1 || vneeded==3 )
+            {
+                vr = new double[n+1, n+1];
+                for(i=1; i<=n; i++)
+                {
+                    for(i_=1; i_<=n;i_++)
+                    {
+                        vr[i,i_] = s[i,i_];
+                    }
+                }
+            }
+            if( vneeded==2 || vneeded==3 )
+            {
+                vl = new double[n+1, n+1];
+                for(i=1; i<=n; i++)
+                {
+                    for(i_=1; i_<=n;i_++)
+                    {
+                        vl[i,i_] = s[i,i_];
+                    }
+                }
+            }
+            internaltrevc(a, n, vneeded, 1, sel, ref vl, ref vr, ref m, ref info);
+            result = info==0;
+            return result;
+        }
+
+
+        private static void toupperhessenberg(ref double[,] a,
+            int n,
+            ref double[] tau)
+        {
+            int i = 0;
+            int ip1 = 0;
+            int nmi = 0;
+            double v = 0;
+            double[] t = new double[0];
+            double[] work = new double[0];
+            int i_ = 0;
+            int i1_ = 0;
+
+            tau = new double[0];
+
+            alglib.ap.assert(n>=0, "ToUpperHessenberg: incorrect N!");
+            
+            //
+            // Quick return if possible
+            //
+            if( n<=1 )
+            {
+                return;
+            }
+            tau = new double[n-1+1];
+            t = new double[n+1];
+            work = new double[n+1];
+            for(i=1; i<=n-1; i++)
+            {
+                
+                //
+                // Compute elementary reflector H(i) to annihilate A(i+2:ihi,i)
+                //
+                ip1 = i+1;
+                nmi = n-i;
+                i1_ = (ip1) - (1);
+                for(i_=1; i_<=nmi;i_++)
+                {
+                    t[i_] = a[i_+i1_,i];
+                }
+                reflections.generatereflection(ref t, nmi, ref v);
+                i1_ = (1) - (ip1);
+                for(i_=ip1; i_<=n;i_++)
+                {
+                    a[i_,i] = t[i_+i1_];
+                }
+                tau[i] = v;
+                t[1] = 1;
+                
+                //
+                // Apply H(i) to A(1:ihi,i+1:ihi) from the right
+                //
+                reflections.applyreflectionfromtheright(ref a, v, t, 1, n, i+1, n, ref work);
+                
+                //
+                // Apply H(i) to A(i+1:ihi,i+1:n) from the left
+                //
+                reflections.applyreflectionfromtheleft(ref a, v, t, i+1, n, i+1, n, ref work);
+            }
+        }
+
+
+        private static void unpackqfromupperhessenberg(double[,] a,
+            int n,
+            double[] tau,
+            ref double[,] q)
+        {
+            int i = 0;
+            int j = 0;
+            double[] v = new double[0];
+            double[] work = new double[0];
+            int ip1 = 0;
+            int nmi = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            q = new double[0,0];
+
+            if( n==0 )
+            {
+                return;
+            }
+            
+            //
+            // init
+            //
+            q = new double[n+1, n+1];
+            v = new double[n+1];
+            work = new double[n+1];
+            for(i=1; i<=n; i++)
+            {
+                for(j=1; j<=n; j++)
+                {
+                    if( i==j )
+                    {
+                        q[i,j] = 1;
+                    }
+                    else
+                    {
+                        q[i,j] = 0;
+                    }
+                }
+            }
+            
+            //
+            // unpack Q
+            //
+            for(i=1; i<=n-1; i++)
+            {
+                
+                //
+                // Apply H(i)
+                //
+                ip1 = i+1;
+                nmi = n-i;
+                i1_ = (ip1) - (1);
+                for(i_=1; i_<=nmi;i_++)
+                {
+                    v[i_] = a[i_+i1_,i];
+                }
+                v[1] = 1;
+                reflections.applyreflectionfromtheright(ref q, tau[i], v, 1, n, i+1, n, ref work);
+            }
+        }
+
+
+    }
+    public class matgen
+    {
+        /*************************************************************************
+        Generation of a random uniformly distributed (Haar) orthogonal matrix
+
+        INPUT PARAMETERS:
+            N   -   matrix size, N>=1
+            
+        OUTPUT PARAMETERS:
+            A   -   orthogonal NxN matrix, array[0..N-1,0..N-1]
+
+          -- ALGLIB routine --
+             04.12.2009
+             Bochkanov Sergey
+        *************************************************************************/
+        public static void rmatrixrndorthogonal(int n,
+            ref double[,] a)
+        {
+            int i = 0;
+            int j = 0;
+
+            a = new double[0,0];
+
+            alglib.ap.assert(n>=1, "RMatrixRndOrthogonal: N<1!");
+            a = new double[n, n];
+            for(i=0; i<=n-1; i++)
+            {
+                for(j=0; j<=n-1; j++)
+                {
+                    if( i==j )
+                    {
+                        a[i,j] = 1;
+                    }
+                    else
+                    {
+                        a[i,j] = 0;
+                    }
+                }
+            }
+            rmatrixrndorthogonalfromtheright(ref a, n, n);
+        }
+
+
+        /*************************************************************************
+        Generation of random NxN matrix with given condition number and norm2(A)=1
+
+        INPUT PARAMETERS:
+            N   -   matrix size
+            C   -   condition number (in 2-norm)
+
+        OUTPUT PARAMETERS:
+            A   -   random matrix with norm2(A)=1 and cond(A)=C
+
+          -- ALGLIB routine --
+             04.12.2009
+             Bochkanov Sergey
+        *************************************************************************/
+        public static void rmatrixrndcond(int n,
+            double c,
+            ref double[,] a)
+        {
+            int i = 0;
+            int j = 0;
+            double l1 = 0;
+            double l2 = 0;
+
+            a = new double[0,0];
+
+            alglib.ap.assert(n>=1 && (double)(c)>=(double)(1), "RMatrixRndCond: N<1 or C<1!");
+            a = new double[n, n];
+            if( n==1 )
+            {
+                
+                //
+                // special case
+                //
+                a[0,0] = 2*math.randominteger(2)-1;
+                return;
+            }
+            l1 = 0;
+            l2 = Math.Log(1/c);
+            for(i=0; i<=n-1; i++)
+            {
+                for(j=0; j<=n-1; j++)
+                {
+                    a[i,j] = 0;
+                }
+            }
+            a[0,0] = Math.Exp(l1);
+            for(i=1; i<=n-2; i++)
+            {
+                a[i,i] = Math.Exp(math.randomreal()*(l2-l1)+l1);
+            }
+            a[n-1,n-1] = Math.Exp(l2);
+            rmatrixrndorthogonalfromtheleft(ref a, n, n);
+            rmatrixrndorthogonalfromtheright(ref a, n, n);
+        }
+
+
+        /*************************************************************************
+        Generation of a random Haar distributed orthogonal complex matrix
+
+        INPUT PARAMETERS:
+            N   -   matrix size, N>=1
+
+        OUTPUT PARAMETERS:
+            A   -   orthogonal NxN matrix, array[0..N-1,0..N-1]
+
+          -- ALGLIB routine --
+             04.12.2009
+             Bochkanov Sergey
+        *************************************************************************/
+        public static void cmatrixrndorthogonal(int n,
+            ref complex[,] a)
+        {
+            int i = 0;
+            int j = 0;
+
+            a = new complex[0,0];
+
+            alglib.ap.assert(n>=1, "CMatrixRndOrthogonal: N<1!");
+            a = new complex[n, n];
+            for(i=0; i<=n-1; i++)
+            {
+                for(j=0; j<=n-1; j++)
+                {
+                    if( i==j )
+                    {
+                        a[i,j] = 1;
+                    }
+                    else
+                    {
+                        a[i,j] = 0;
+                    }
+                }
+            }
+            cmatrixrndorthogonalfromtheright(ref a, n, n);
+        }
+
+
+        /*************************************************************************
+        Generation of random NxN complex matrix with given condition number C and
+        norm2(A)=1
+
+        INPUT PARAMETERS:
+            N   -   matrix size
+            C   -   condition number (in 2-norm)
+
+        OUTPUT PARAMETERS:
+            A   -   random matrix with norm2(A)=1 and cond(A)=C
+
+          -- ALGLIB routine --
+             04.12.2009
+             Bochkanov Sergey
+        *************************************************************************/
+        public static void cmatrixrndcond(int n,
+            double c,
+            ref complex[,] a)
+        {
+            int i = 0;
+            int j = 0;
+            double l1 = 0;
+            double l2 = 0;
+            hqrnd.hqrndstate state = new hqrnd.hqrndstate();
+            complex v = 0;
+
+            a = new complex[0,0];
+
+            alglib.ap.assert(n>=1 && (double)(c)>=(double)(1), "CMatrixRndCond: N<1 or C<1!");
+            a = new complex[n, n];
+            if( n==1 )
+            {
+                
+                //
+                // special case
+                //
+                hqrnd.hqrndrandomize(state);
+                hqrnd.hqrndunit2(state, ref v.x, ref v.y);
+                a[0,0] = v;
+                return;
+            }
+            l1 = 0;
+            l2 = Math.Log(1/c);
+            for(i=0; i<=n-1; i++)
+            {
+                for(j=0; j<=n-1; j++)
+                {
+                    a[i,j] = 0;
+                }
+            }
+            a[0,0] = Math.Exp(l1);
+            for(i=1; i<=n-2; i++)
+            {
+                a[i,i] = Math.Exp(math.randomreal()*(l2-l1)+l1);
+            }
+            a[n-1,n-1] = Math.Exp(l2);
+            cmatrixrndorthogonalfromtheleft(ref a, n, n);
+            cmatrixrndorthogonalfromtheright(ref a, n, n);
+        }
+
+
+        /*************************************************************************
+        Generation of random NxN symmetric matrix with given condition number  and
+        norm2(A)=1
+
+        INPUT PARAMETERS:
+            N   -   matrix size
+            C   -   condition number (in 2-norm)
+
+        OUTPUT PARAMETERS:
+            A   -   random matrix with norm2(A)=1 and cond(A)=C
+
+          -- ALGLIB routine --
+             04.12.2009
+             Bochkanov Sergey
+        *************************************************************************/
+        public static void smatrixrndcond(int n,
+            double c,
+            ref double[,] a)
+        {
+            int i = 0;
+            int j = 0;
+            double l1 = 0;
+            double l2 = 0;
+
+            a = new double[0,0];
+
+            alglib.ap.assert(n>=1 && (double)(c)>=(double)(1), "SMatrixRndCond: N<1 or C<1!");
+            a = new double[n, n];
+            if( n==1 )
+            {
+                
+                //
+                // special case
+                //
+                a[0,0] = 2*math.randominteger(2)-1;
+                return;
+            }
+            
+            //
+            // Prepare matrix
+            //
+            l1 = 0;
+            l2 = Math.Log(1/c);
+            for(i=0; i<=n-1; i++)
+            {
+                for(j=0; j<=n-1; j++)
+                {
+                    a[i,j] = 0;
+                }
+            }
+            a[0,0] = Math.Exp(l1);
+            for(i=1; i<=n-2; i++)
+            {
+                a[i,i] = (2*math.randominteger(2)-1)*Math.Exp(math.randomreal()*(l2-l1)+l1);
+            }
+            a[n-1,n-1] = Math.Exp(l2);
+            
+            //
+            // Multiply
+            //
+            smatrixrndmultiply(ref a, n);
+        }
+
+
+        /*************************************************************************
+        Generation of random NxN symmetric positive definite matrix with given
+        condition number and norm2(A)=1
+
+        INPUT PARAMETERS:
+            N   -   matrix size
+            C   -   condition number (in 2-norm)
+
+        OUTPUT PARAMETERS:
+            A   -   random SPD matrix with norm2(A)=1 and cond(A)=C
+
+          -- ALGLIB routine --
+             04.12.2009
+             Bochkanov Sergey
+        *************************************************************************/
+        public static void spdmatrixrndcond(int n,
+            double c,
+            ref double[,] a)
+        {
+            int i = 0;
+            int j = 0;
+            double l1 = 0;
+            double l2 = 0;
+
+            a = new double[0,0];
+
+            
+            //
+            // Special cases
+            //
+            if( n<=0 || (double)(c)<(double)(1) )
+            {
+                return;
+            }
+            a = new double[n, n];
+            if( n==1 )
+            {
+                a[0,0] = 1;
+                return;
+            }
+            
+            //
+            // Prepare matrix
+            //
+            l1 = 0;
+            l2 = Math.Log(1/c);
+            for(i=0; i<=n-1; i++)
+            {
+                for(j=0; j<=n-1; j++)
+                {
+                    a[i,j] = 0;
+                }
+            }
+            a[0,0] = Math.Exp(l1);
+            for(i=1; i<=n-2; i++)
+            {
+                a[i,i] = Math.Exp(math.randomreal()*(l2-l1)+l1);
+            }
+            a[n-1,n-1] = Math.Exp(l2);
+            
+            //
+            // Multiply
+            //
+            smatrixrndmultiply(ref a, n);
+        }
+
+
+        /*************************************************************************
+        Generation of random NxN Hermitian matrix with given condition number  and
+        norm2(A)=1
+
+        INPUT PARAMETERS:
+            N   -   matrix size
+            C   -   condition number (in 2-norm)
+
+        OUTPUT PARAMETERS:
+            A   -   random matrix with norm2(A)=1 and cond(A)=C
+
+          -- ALGLIB routine --
+             04.12.2009
+             Bochkanov Sergey
+        *************************************************************************/
+        public static void hmatrixrndcond(int n,
+            double c,
+            ref complex[,] a)
+        {
+            int i = 0;
+            int j = 0;
+            double l1 = 0;
+            double l2 = 0;
+
+            a = new complex[0,0];
+
+            alglib.ap.assert(n>=1 && (double)(c)>=(double)(1), "HMatrixRndCond: N<1 or C<1!");
+            a = new complex[n, n];
+            if( n==1 )
+            {
+                
+                //
+                // special case
+                //
+                a[0,0] = 2*math.randominteger(2)-1;
+                return;
+            }
+            
+            //
+            // Prepare matrix
+            //
+            l1 = 0;
+            l2 = Math.Log(1/c);
+            for(i=0; i<=n-1; i++)
+            {
+                for(j=0; j<=n-1; j++)
+                {
+                    a[i,j] = 0;
+                }
+            }
+            a[0,0] = Math.Exp(l1);
+            for(i=1; i<=n-2; i++)
+            {
+                a[i,i] = (2*math.randominteger(2)-1)*Math.Exp(math.randomreal()*(l2-l1)+l1);
+            }
+            a[n-1,n-1] = Math.Exp(l2);
+            
+            //
+            // Multiply
+            //
+            hmatrixrndmultiply(ref a, n);
+            
+            //
+            // post-process to ensure that matrix diagonal is real
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                a[i,i].y = 0;
+            }
+        }
+
+
+        /*************************************************************************
+        Generation of random NxN Hermitian positive definite matrix with given
+        condition number and norm2(A)=1
+
+        INPUT PARAMETERS:
+            N   -   matrix size
+            C   -   condition number (in 2-norm)
+
+        OUTPUT PARAMETERS:
+            A   -   random HPD matrix with norm2(A)=1 and cond(A)=C
+
+          -- ALGLIB routine --
+             04.12.2009
+             Bochkanov Sergey
+        *************************************************************************/
+        public static void hpdmatrixrndcond(int n,
+            double c,
+            ref complex[,] a)
+        {
+            int i = 0;
+            int j = 0;
+            double l1 = 0;
+            double l2 = 0;
+
+            a = new complex[0,0];
+
+            
+            //
+            // Special cases
+            //
+            if( n<=0 || (double)(c)<(double)(1) )
+            {
+                return;
+            }
+            a = new complex[n, n];
+            if( n==1 )
+            {
+                a[0,0] = 1;
+                return;
+            }
+            
+            //
+            // Prepare matrix
+            //
+            l1 = 0;
+            l2 = Math.Log(1/c);
+            for(i=0; i<=n-1; i++)
+            {
+                for(j=0; j<=n-1; j++)
+                {
+                    a[i,j] = 0;
+                }
+            }
+            a[0,0] = Math.Exp(l1);
+            for(i=1; i<=n-2; i++)
+            {
+                a[i,i] = Math.Exp(math.randomreal()*(l2-l1)+l1);
+            }
+            a[n-1,n-1] = Math.Exp(l2);
+            
+            //
+            // Multiply
+            //
+            hmatrixrndmultiply(ref a, n);
+            
+            //
+            // post-process to ensure that matrix diagonal is real
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                a[i,i].y = 0;
+            }
+        }
+
+
+        /*************************************************************************
+        Multiplication of MxN matrix by NxN random Haar distributed orthogonal matrix
+
+        INPUT PARAMETERS:
+            A   -   matrix, array[0..M-1, 0..N-1]
+            M, N-   matrix size
+
+        OUTPUT PARAMETERS:
+            A   -   A*Q, where Q is random NxN orthogonal matrix
+
+          -- ALGLIB routine --
+             04.12.2009
+             Bochkanov Sergey
+        *************************************************************************/
+        public static void rmatrixrndorthogonalfromtheright(ref double[,] a,
+            int m,
+            int n)
+        {
+            double tau = 0;
+            double lambdav = 0;
+            int s = 0;
+            int i = 0;
+            double u1 = 0;
+            double u2 = 0;
+            double[] w = new double[0];
+            double[] v = new double[0];
+            hqrnd.hqrndstate state = new hqrnd.hqrndstate();
+            int i_ = 0;
+
+            alglib.ap.assert(n>=1 && m>=1, "RMatrixRndOrthogonalFromTheRight: N<1 or M<1!");
+            if( n==1 )
+            {
+                
+                //
+                // Special case
+                //
+                tau = 2*math.randominteger(2)-1;
+                for(i=0; i<=m-1; i++)
+                {
+                    a[i,0] = a[i,0]*tau;
+                }
+                return;
+            }
+            
+            //
+            // General case.
+            // First pass.
+            //
+            w = new double[m];
+            v = new double[n+1];
+            hqrnd.hqrndrandomize(state);
+            for(s=2; s<=n; s++)
+            {
+                
+                //
+                // Prepare random normal v
+                //
+                do
+                {
+                    i = 1;
+                    while( i<=s )
+                    {
+                        hqrnd.hqrndnormal2(state, ref u1, ref u2);
+                        v[i] = u1;
+                        if( i+1<=s )
+                        {
+                            v[i+1] = u2;
+                        }
+                        i = i+2;
+                    }
+                    lambdav = 0.0;
+                    for(i_=1; i_<=s;i_++)
+                    {
+                        lambdav += v[i_]*v[i_];
+                    }
+                }
+                while( (double)(lambdav)==(double)(0) );
+                
+                //
+                // Prepare and apply reflection
+                //
+                reflections.generatereflection(ref v, s, ref tau);
+                v[1] = 1;
+                reflections.applyreflectionfromtheright(ref a, tau, v, 0, m-1, n-s, n-1, ref w);
+            }
+            
+            //
+            // Second pass.
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                tau = 2*math.randominteger(2)-1;
+                for(i_=0; i_<=m-1;i_++)
+                {
+                    a[i_,i] = tau*a[i_,i];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Multiplication of MxN matrix by MxM random Haar distributed orthogonal matrix
+
+        INPUT PARAMETERS:
+            A   -   matrix, array[0..M-1, 0..N-1]
+            M, N-   matrix size
+
+        OUTPUT PARAMETERS:
+            A   -   Q*A, where Q is random MxM orthogonal matrix
+
+          -- ALGLIB routine --
+             04.12.2009
+             Bochkanov Sergey
+        *************************************************************************/
+        public static void rmatrixrndorthogonalfromtheleft(ref double[,] a,
+            int m,
+            int n)
+        {
+            double tau = 0;
+            double lambdav = 0;
+            int s = 0;
+            int i = 0;
+            int j = 0;
+            double u1 = 0;
+            double u2 = 0;
+            double[] w = new double[0];
+            double[] v = new double[0];
+            hqrnd.hqrndstate state = new hqrnd.hqrndstate();
+            int i_ = 0;
+
+            alglib.ap.assert(n>=1 && m>=1, "RMatrixRndOrthogonalFromTheRight: N<1 or M<1!");
+            if( m==1 )
+            {
+                
+                //
+                // special case
+                //
+                tau = 2*math.randominteger(2)-1;
+                for(j=0; j<=n-1; j++)
+                {
+                    a[0,j] = a[0,j]*tau;
+                }
+                return;
+            }
+            
+            //
+            // General case.
+            // First pass.
+            //
+            w = new double[n];
+            v = new double[m+1];
+            hqrnd.hqrndrandomize(state);
+            for(s=2; s<=m; s++)
+            {
+                
+                //
+                // Prepare random normal v
+                //
+                do
+                {
+                    i = 1;
+                    while( i<=s )
+                    {
+                        hqrnd.hqrndnormal2(state, ref u1, ref u2);
+                        v[i] = u1;
+                        if( i+1<=s )
+                        {
+                            v[i+1] = u2;
+                        }
+                        i = i+2;
+                    }
+                    lambdav = 0.0;
+                    for(i_=1; i_<=s;i_++)
+                    {
+                        lambdav += v[i_]*v[i_];
+                    }
+                }
+                while( (double)(lambdav)==(double)(0) );
+                
+                //
+                // Prepare and apply reflection
+                //
+                reflections.generatereflection(ref v, s, ref tau);
+                v[1] = 1;
+                reflections.applyreflectionfromtheleft(ref a, tau, v, m-s, m-1, 0, n-1, ref w);
+            }
+            
+            //
+            // Second pass.
+            //
+            for(i=0; i<=m-1; i++)
+            {
+                tau = 2*math.randominteger(2)-1;
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    a[i,i_] = tau*a[i,i_];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Multiplication of MxN complex matrix by NxN random Haar distributed
+        complex orthogonal matrix
+
+        INPUT PARAMETERS:
+            A   -   matrix, array[0..M-1, 0..N-1]
+            M, N-   matrix size
+
+        OUTPUT PARAMETERS:
+            A   -   A*Q, where Q is random NxN orthogonal matrix
+
+          -- ALGLIB routine --
+             04.12.2009
+             Bochkanov Sergey
+        *************************************************************************/
+        public static void cmatrixrndorthogonalfromtheright(ref complex[,] a,
+            int m,
+            int n)
+        {
+            complex lambdav = 0;
+            complex tau = 0;
+            int s = 0;
+            int i = 0;
+            complex[] w = new complex[0];
+            complex[] v = new complex[0];
+            hqrnd.hqrndstate state = new hqrnd.hqrndstate();
+            int i_ = 0;
+
+            alglib.ap.assert(n>=1 && m>=1, "CMatrixRndOrthogonalFromTheRight: N<1 or M<1!");
+            if( n==1 )
+            {
+                
+                //
+                // Special case
+                //
+                hqrnd.hqrndrandomize(state);
+                hqrnd.hqrndunit2(state, ref tau.x, ref tau.y);
+                for(i=0; i<=m-1; i++)
+                {
+                    a[i,0] = a[i,0]*tau;
+                }
+                return;
+            }
+            
+            //
+            // General case.
+            // First pass.
+            //
+            w = new complex[m];
+            v = new complex[n+1];
+            hqrnd.hqrndrandomize(state);
+            for(s=2; s<=n; s++)
+            {
+                
+                //
+                // Prepare random normal v
+                //
+                do
+                {
+                    for(i=1; i<=s; i++)
+                    {
+                        hqrnd.hqrndnormal2(state, ref tau.x, ref tau.y);
+                        v[i] = tau;
+                    }
+                    lambdav = 0.0;
+                    for(i_=1; i_<=s;i_++)
+                    {
+                        lambdav += v[i_]*math.conj(v[i_]);
+                    }
+                }
+                while( lambdav==0 );
+                
+                //
+                // Prepare and apply reflection
+                //
+                creflections.complexgeneratereflection(ref v, s, ref tau);
+                v[1] = 1;
+                creflections.complexapplyreflectionfromtheright(ref a, tau, ref v, 0, m-1, n-s, n-1, ref w);
+            }
+            
+            //
+            // Second pass.
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                hqrnd.hqrndunit2(state, ref tau.x, ref tau.y);
+                for(i_=0; i_<=m-1;i_++)
+                {
+                    a[i_,i] = tau*a[i_,i];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Multiplication of MxN complex matrix by MxM random Haar distributed
+        complex orthogonal matrix
+
+        INPUT PARAMETERS:
+            A   -   matrix, array[0..M-1, 0..N-1]
+            M, N-   matrix size
+
+        OUTPUT PARAMETERS:
+            A   -   Q*A, where Q is random MxM orthogonal matrix
+
+          -- ALGLIB routine --
+             04.12.2009
+             Bochkanov Sergey
+        *************************************************************************/
+        public static void cmatrixrndorthogonalfromtheleft(ref complex[,] a,
+            int m,
+            int n)
+        {
+            complex tau = 0;
+            complex lambdav = 0;
+            int s = 0;
+            int i = 0;
+            int j = 0;
+            complex[] w = new complex[0];
+            complex[] v = new complex[0];
+            hqrnd.hqrndstate state = new hqrnd.hqrndstate();
+            int i_ = 0;
+
+            alglib.ap.assert(n>=1 && m>=1, "CMatrixRndOrthogonalFromTheRight: N<1 or M<1!");
+            if( m==1 )
+            {
+                
+                //
+                // special case
+                //
+                hqrnd.hqrndrandomize(state);
+                hqrnd.hqrndunit2(state, ref tau.x, ref tau.y);
+                for(j=0; j<=n-1; j++)
+                {
+                    a[0,j] = a[0,j]*tau;
+                }
+                return;
+            }
+            
+            //
+            // General case.
+            // First pass.
+            //
+            w = new complex[n];
+            v = new complex[m+1];
+            hqrnd.hqrndrandomize(state);
+            for(s=2; s<=m; s++)
+            {
+                
+                //
+                // Prepare random normal v
+                //
+                do
+                {
+                    for(i=1; i<=s; i++)
+                    {
+                        hqrnd.hqrndnormal2(state, ref tau.x, ref tau.y);
+                        v[i] = tau;
+                    }
+                    lambdav = 0.0;
+                    for(i_=1; i_<=s;i_++)
+                    {
+                        lambdav += v[i_]*math.conj(v[i_]);
+                    }
+                }
+                while( lambdav==0 );
+                
+                //
+                // Prepare and apply reflection
+                //
+                creflections.complexgeneratereflection(ref v, s, ref tau);
+                v[1] = 1;
+                creflections.complexapplyreflectionfromtheleft(ref a, tau, v, m-s, m-1, 0, n-1, ref w);
+            }
+            
+            //
+            // Second pass.
+            //
+            for(i=0; i<=m-1; i++)
+            {
+                hqrnd.hqrndunit2(state, ref tau.x, ref tau.y);
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    a[i,i_] = tau*a[i,i_];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Symmetric multiplication of NxN matrix by random Haar distributed
+        orthogonal  matrix
+
+        INPUT PARAMETERS:
+            A   -   matrix, array[0..N-1, 0..N-1]
+            N   -   matrix size
+
+        OUTPUT PARAMETERS:
+            A   -   Q'*A*Q, where Q is random NxN orthogonal matrix
+
+          -- ALGLIB routine --
+             04.12.2009
+             Bochkanov Sergey
+        *************************************************************************/
+        public static void smatrixrndmultiply(ref double[,] a,
+            int n)
+        {
+            double tau = 0;
+            double lambdav = 0;
+            int s = 0;
+            int i = 0;
+            double u1 = 0;
+            double u2 = 0;
+            double[] w = new double[0];
+            double[] v = new double[0];
+            hqrnd.hqrndstate state = new hqrnd.hqrndstate();
+            int i_ = 0;
+
+            
+            //
+            // General case.
+            //
+            w = new double[n];
+            v = new double[n+1];
+            hqrnd.hqrndrandomize(state);
+            for(s=2; s<=n; s++)
+            {
+                
+                //
+                // Prepare random normal v
+                //
+                do
+                {
+                    i = 1;
+                    while( i<=s )
+                    {
+                        hqrnd.hqrndnormal2(state, ref u1, ref u2);
+                        v[i] = u1;
+                        if( i+1<=s )
+                        {
+                            v[i+1] = u2;
+                        }
+                        i = i+2;
+                    }
+                    lambdav = 0.0;
+                    for(i_=1; i_<=s;i_++)
+                    {
+                        lambdav += v[i_]*v[i_];
+                    }
+                }
+                while( (double)(lambdav)==(double)(0) );
+                
+                //
+                // Prepare and apply reflection
+                //
+                reflections.generatereflection(ref v, s, ref tau);
+                v[1] = 1;
+                reflections.applyreflectionfromtheright(ref a, tau, v, 0, n-1, n-s, n-1, ref w);
+                reflections.applyreflectionfromtheleft(ref a, tau, v, n-s, n-1, 0, n-1, ref w);
+            }
+            
+            //
+            // Second pass.
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                tau = 2*math.randominteger(2)-1;
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    a[i_,i] = tau*a[i_,i];
+                }
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    a[i,i_] = tau*a[i,i_];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Hermitian multiplication of NxN matrix by random Haar distributed
+        complex orthogonal matrix
+
+        INPUT PARAMETERS:
+            A   -   matrix, array[0..N-1, 0..N-1]
+            N   -   matrix size
+
+        OUTPUT PARAMETERS:
+            A   -   Q^H*A*Q, where Q is random NxN orthogonal matrix
+
+          -- ALGLIB routine --
+             04.12.2009
+             Bochkanov Sergey
+        *************************************************************************/
+        public static void hmatrixrndmultiply(ref complex[,] a,
+            int n)
+        {
+            complex tau = 0;
+            complex lambdav = 0;
+            int s = 0;
+            int i = 0;
+            complex[] w = new complex[0];
+            complex[] v = new complex[0];
+            hqrnd.hqrndstate state = new hqrnd.hqrndstate();
+            int i_ = 0;
+
+            
+            //
+            // General case.
+            //
+            w = new complex[n];
+            v = new complex[n+1];
+            hqrnd.hqrndrandomize(state);
+            for(s=2; s<=n; s++)
+            {
+                
+                //
+                // Prepare random normal v
+                //
+                do
+                {
+                    for(i=1; i<=s; i++)
+                    {
+                        hqrnd.hqrndnormal2(state, ref tau.x, ref tau.y);
+                        v[i] = tau;
+                    }
+                    lambdav = 0.0;
+                    for(i_=1; i_<=s;i_++)
+                    {
+                        lambdav += v[i_]*math.conj(v[i_]);
+                    }
+                }
+                while( lambdav==0 );
+                
+                //
+                // Prepare and apply reflection
+                //
+                creflections.complexgeneratereflection(ref v, s, ref tau);
+                v[1] = 1;
+                creflections.complexapplyreflectionfromtheright(ref a, tau, ref v, 0, n-1, n-s, n-1, ref w);
+                creflections.complexapplyreflectionfromtheleft(ref a, math.conj(tau), v, n-s, n-1, 0, n-1, ref w);
+            }
+            
+            //
+            // Second pass.
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                hqrnd.hqrndunit2(state, ref tau.x, ref tau.y);
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    a[i_,i] = tau*a[i_,i];
+                }
+                tau = math.conj(tau);
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    a[i,i_] = tau*a[i,i_];
+                }
+            }
+        }
+
+
+    }
+    public class trfac
+    {
+        /*************************************************************************
+        LU decomposition of a general real matrix with row pivoting
+
+        A is represented as A = P*L*U, where:
+        * L is lower unitriangular matrix
+        * U is upper triangular matrix
+        * P = P0*P1*...*PK, K=min(M,N)-1,
+          Pi - permutation matrix for I and Pivots[I]
+
+        This is cache-oblivous implementation of LU decomposition.
+        It is optimized for square matrices. As for rectangular matrices:
+        * best case - M>>N
+        * worst case - N>>M, small M, large N, matrix does not fit in CPU cache
+
+        INPUT PARAMETERS:
+            A       -   array[0..M-1, 0..N-1].
+            M       -   number of rows in matrix A.
+            N       -   number of columns in matrix A.
+
+
+        OUTPUT PARAMETERS:
+            A       -   matrices L and U in compact form:
+                        * L is stored under main diagonal
+                        * U is stored on and above main diagonal
+            Pivots  -   permutation matrix in compact form.
+                        array[0..Min(M-1,N-1)].
+
+          -- ALGLIB routine --
+             10.01.2010
+             Bochkanov Sergey
+        *************************************************************************/
+        public static void rmatrixlu(ref double[,] a,
+            int m,
+            int n,
+            ref int[] pivots)
+        {
+            pivots = new int[0];
+
+            alglib.ap.assert(m>0, "RMatrixLU: incorrect M!");
+            alglib.ap.assert(n>0, "RMatrixLU: incorrect N!");
+            rmatrixplu(ref a, m, n, ref pivots);
+        }
+
+
+        /*************************************************************************
+        LU decomposition of a general complex matrix with row pivoting
+
+        A is represented as A = P*L*U, where:
+        * L is lower unitriangular matrix
+        * U is upper triangular matrix
+        * P = P0*P1*...*PK, K=min(M,N)-1,
+          Pi - permutation matrix for I and Pivots[I]
+
+        This is cache-oblivous implementation of LU decomposition. It is optimized
+        for square matrices. As for rectangular matrices:
+        * best case - M>>N
+        * worst case - N>>M, small M, large N, matrix does not fit in CPU cache
+
+        INPUT PARAMETERS:
+            A       -   array[0..M-1, 0..N-1].
+            M       -   number of rows in matrix A.
+            N       -   number of columns in matrix A.
+
+
+        OUTPUT PARAMETERS:
+            A       -   matrices L and U in compact form:
+                        * L is stored under main diagonal
+                        * U is stored on and above main diagonal
+            Pivots  -   permutation matrix in compact form.
+                        array[0..Min(M-1,N-1)].
+
+          -- ALGLIB routine --
+             10.01.2010
+             Bochkanov Sergey
+        *************************************************************************/
+        public static void cmatrixlu(ref complex[,] a,
+            int m,
+            int n,
+            ref int[] pivots)
+        {
+            pivots = new int[0];
+
+            alglib.ap.assert(m>0, "CMatrixLU: incorrect M!");
+            alglib.ap.assert(n>0, "CMatrixLU: incorrect N!");
+            cmatrixplu(ref a, m, n, ref pivots);
+        }
+
+
+        /*************************************************************************
+        Cache-oblivious Cholesky decomposition
+
+        The algorithm computes Cholesky decomposition  of  a  Hermitian  positive-
+        definite matrix. The result of an algorithm is a representation  of  A  as
+        A=U'*U  or A=L*L' (here X' detones conj(X^T)).
+
+        INPUT PARAMETERS:
+            A       -   upper or lower triangle of a factorized matrix.
+                        array with elements [0..N-1, 0..N-1].
+            N       -   size of matrix A.
+            IsUpper -   if IsUpper=True, then A contains an upper triangle of
+                        a symmetric matrix, otherwise A contains a lower one.
+
+        OUTPUT PARAMETERS:
+            A       -   the result of factorization. If IsUpper=True, then
+                        the upper triangle contains matrix U, so that A = U'*U,
+                        and the elements below the main diagonal are not modified.
+                        Similarly, if IsUpper = False.
+
+        RESULT:
+            If  the  matrix  is  positive-definite,  the  function  returns  True.
+            Otherwise, the function returns False. Contents of A is not determined
+            in such case.
+
+          -- ALGLIB routine --
+             15.12.2009
+             Bochkanov Sergey
+        *************************************************************************/
+        public static bool hpdmatrixcholesky(ref complex[,] a,
+            int n,
+            bool isupper)
+        {
+            bool result = new bool();
+            complex[] tmp = new complex[0];
+
+            if( n<1 )
+            {
+                result = false;
+                return result;
+            }
+            result = hpdmatrixcholeskyrec(ref a, 0, n, isupper, ref tmp);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Cache-oblivious Cholesky decomposition
+
+        The algorithm computes Cholesky decomposition  of  a  symmetric  positive-
+        definite matrix. The result of an algorithm is a representation  of  A  as
+        A=U^T*U  or A=L*L^T
+
+        INPUT PARAMETERS:
+            A       -   upper or lower triangle of a factorized matrix.
+                        array with elements [0..N-1, 0..N-1].
+            N       -   size of matrix A.
+            IsUpper -   if IsUpper=True, then A contains an upper triangle of
+                        a symmetric matrix, otherwise A contains a lower one.
+
+        OUTPUT PARAMETERS:
+            A       -   the result of factorization. If IsUpper=True, then
+                        the upper triangle contains matrix U, so that A = U^T*U,
+                        and the elements below the main diagonal are not modified.
+                        Similarly, if IsUpper = False.
+
+        RESULT:
+            If  the  matrix  is  positive-definite,  the  function  returns  True.
+            Otherwise, the function returns False. Contents of A is not determined
+            in such case.
+
+          -- ALGLIB routine --
+             15.12.2009
+             Bochkanov Sergey
+        *************************************************************************/
+        public static bool spdmatrixcholesky(ref double[,] a,
+            int n,
+            bool isupper)
+        {
+            bool result = new bool();
+            double[] tmp = new double[0];
+
+            if( n<1 )
+            {
+                result = false;
+                return result;
+            }
+            result = spdmatrixcholeskyrec(ref a, 0, n, isupper, ref tmp);
+            return result;
+        }
+
+
+        public static void rmatrixlup(ref double[,] a,
+            int m,
+            int n,
+            ref int[] pivots)
+        {
+            double[] tmp = new double[0];
+            int i = 0;
+            int j = 0;
+            double mx = 0;
+            double v = 0;
+            int i_ = 0;
+
+            pivots = new int[0];
+
+            
+            //
+            // Internal LU decomposition subroutine.
+            // Never call it directly.
+            //
+            alglib.ap.assert(m>0, "RMatrixLUP: incorrect M!");
+            alglib.ap.assert(n>0, "RMatrixLUP: incorrect N!");
+            
+            //
+            // Scale matrix to avoid overflows,
+            // decompose it, then scale back.
+            //
+            mx = 0;
+            for(i=0; i<=m-1; i++)
+            {
+                for(j=0; j<=n-1; j++)
+                {
+                    mx = Math.Max(mx, Math.Abs(a[i,j]));
+                }
+            }
+            if( (double)(mx)!=(double)(0) )
+            {
+                v = 1/mx;
+                for(i=0; i<=m-1; i++)
+                {
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        a[i,i_] = v*a[i,i_];
+                    }
+                }
+            }
+            pivots = new int[Math.Min(m, n)];
+            tmp = new double[2*Math.Max(m, n)];
+            rmatrixluprec(ref a, 0, m, n, ref pivots, ref tmp);
+            if( (double)(mx)!=(double)(0) )
+            {
+                v = mx;
+                for(i=0; i<=m-1; i++)
+                {
+                    for(i_=0; i_<=Math.Min(i, n-1);i_++)
+                    {
+                        a[i,i_] = v*a[i,i_];
+                    }
+                }
+            }
+        }
+
+
+        public static void cmatrixlup(ref complex[,] a,
+            int m,
+            int n,
+            ref int[] pivots)
+        {
+            complex[] tmp = new complex[0];
+            int i = 0;
+            int j = 0;
+            double mx = 0;
+            double v = 0;
+            int i_ = 0;
+
+            pivots = new int[0];
+
+            
+            //
+            // Internal LU decomposition subroutine.
+            // Never call it directly.
+            //
+            alglib.ap.assert(m>0, "CMatrixLUP: incorrect M!");
+            alglib.ap.assert(n>0, "CMatrixLUP: incorrect N!");
+            
+            //
+            // Scale matrix to avoid overflows,
+            // decompose it, then scale back.
+            //
+            mx = 0;
+            for(i=0; i<=m-1; i++)
+            {
+                for(j=0; j<=n-1; j++)
+                {
+                    mx = Math.Max(mx, math.abscomplex(a[i,j]));
+                }
+            }
+            if( (double)(mx)!=(double)(0) )
+            {
+                v = 1/mx;
+                for(i=0; i<=m-1; i++)
+                {
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        a[i,i_] = v*a[i,i_];
+                    }
+                }
+            }
+            pivots = new int[Math.Min(m, n)];
+            tmp = new complex[2*Math.Max(m, n)];
+            cmatrixluprec(ref a, 0, m, n, ref pivots, ref tmp);
+            if( (double)(mx)!=(double)(0) )
+            {
+                v = mx;
+                for(i=0; i<=m-1; i++)
+                {
+                    for(i_=0; i_<=Math.Min(i, n-1);i_++)
+                    {
+                        a[i,i_] = v*a[i,i_];
+                    }
+                }
+            }
+        }
+
+
+        public static void rmatrixplu(ref double[,] a,
+            int m,
+            int n,
+            ref int[] pivots)
+        {
+            double[] tmp = new double[0];
+            int i = 0;
+            int j = 0;
+            double mx = 0;
+            double v = 0;
+            int i_ = 0;
+
+            pivots = new int[0];
+
+            
+            //
+            // Internal LU decomposition subroutine.
+            // Never call it directly.
+            //
+            alglib.ap.assert(m>0, "RMatrixPLU: incorrect M!");
+            alglib.ap.assert(n>0, "RMatrixPLU: incorrect N!");
+            tmp = new double[2*Math.Max(m, n)];
+            pivots = new int[Math.Min(m, n)];
+            
+            //
+            // Scale matrix to avoid overflows,
+            // decompose it, then scale back.
+            //
+            mx = 0;
+            for(i=0; i<=m-1; i++)
+            {
+                for(j=0; j<=n-1; j++)
+                {
+                    mx = Math.Max(mx, Math.Abs(a[i,j]));
+                }
+            }
+            if( (double)(mx)!=(double)(0) )
+            {
+                v = 1/mx;
+                for(i=0; i<=m-1; i++)
+                {
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        a[i,i_] = v*a[i,i_];
+                    }
+                }
+            }
+            rmatrixplurec(ref a, 0, m, n, ref pivots, ref tmp);
+            if( (double)(mx)!=(double)(0) )
+            {
+                v = mx;
+                for(i=0; i<=Math.Min(m, n)-1; i++)
+                {
+                    for(i_=i; i_<=n-1;i_++)
+                    {
+                        a[i,i_] = v*a[i,i_];
+                    }
+                }
+            }
+        }
+
+
+        public static void cmatrixplu(ref complex[,] a,
+            int m,
+            int n,
+            ref int[] pivots)
+        {
+            complex[] tmp = new complex[0];
+            int i = 0;
+            int j = 0;
+            double mx = 0;
+            complex v = 0;
+            int i_ = 0;
+
+            pivots = new int[0];
+
+            
+            //
+            // Internal LU decomposition subroutine.
+            // Never call it directly.
+            //
+            alglib.ap.assert(m>0, "CMatrixPLU: incorrect M!");
+            alglib.ap.assert(n>0, "CMatrixPLU: incorrect N!");
+            tmp = new complex[2*Math.Max(m, n)];
+            pivots = new int[Math.Min(m, n)];
+            
+            //
+            // Scale matrix to avoid overflows,
+            // decompose it, then scale back.
+            //
+            mx = 0;
+            for(i=0; i<=m-1; i++)
+            {
+                for(j=0; j<=n-1; j++)
+                {
+                    mx = Math.Max(mx, math.abscomplex(a[i,j]));
+                }
+            }
+            if( (double)(mx)!=(double)(0) )
+            {
+                v = 1/mx;
+                for(i=0; i<=m-1; i++)
+                {
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        a[i,i_] = v*a[i,i_];
+                    }
+                }
+            }
+            cmatrixplurec(ref a, 0, m, n, ref pivots, ref tmp);
+            if( (double)(mx)!=(double)(0) )
+            {
+                v = mx;
+                for(i=0; i<=Math.Min(m, n)-1; i++)
+                {
+                    for(i_=i; i_<=n-1;i_++)
+                    {
+                        a[i,i_] = v*a[i,i_];
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Recursive computational subroutine for SPDMatrixCholesky.
+
+        INPUT PARAMETERS:
+            A       -   matrix given by upper or lower triangle
+            Offs    -   offset of diagonal block to decompose
+            N       -   diagonal block size
+            IsUpper -   what half is given
+            Tmp     -   temporary array; allocated by function, if its size is too
+                        small; can be reused on subsequent calls.
+                        
+        OUTPUT PARAMETERS:
+            A       -   upper (or lower) triangle contains Cholesky decomposition
+
+        RESULT:
+            True, on success
+            False, on failure
+
+          -- ALGLIB routine --
+             15.12.2009
+             Bochkanov Sergey
+        *************************************************************************/
+        public static bool spdmatrixcholeskyrec(ref double[,] a,
+            int offs,
+            int n,
+            bool isupper,
+            ref double[] tmp)
+        {
+            bool result = new bool();
+            int n1 = 0;
+            int n2 = 0;
+
+            
+            //
+            // check N
+            //
+            if( n<1 )
+            {
+                result = false;
+                return result;
+            }
+            
+            //
+            // Prepare buffer
+            //
+            if( alglib.ap.len(tmp)<2*n )
+            {
+                tmp = new double[2*n];
+            }
+            
+            //
+            // special cases
+            //
+            if( n==1 )
+            {
+                if( (double)(a[offs,offs])>(double)(0) )
+                {
+                    a[offs,offs] = Math.Sqrt(a[offs,offs]);
+                    result = true;
+                }
+                else
+                {
+                    result = false;
+                }
+                return result;
+            }
+            if( n<=ablas.ablasblocksize(a) )
+            {
+                result = spdmatrixcholesky2(ref a, offs, n, isupper, ref tmp);
+                return result;
+            }
+            
+            //
+            // general case: split task in cache-oblivious manner
+            //
+            result = true;
+            ablas.ablassplitlength(a, n, ref n1, ref n2);
+            result = spdmatrixcholeskyrec(ref a, offs, n1, isupper, ref tmp);
+            if( !result )
+            {
+                return result;
+            }
+            if( n2>0 )
+            {
+                if( isupper )
+                {
+                    ablas.rmatrixlefttrsm(n1, n2, a, offs, offs, isupper, false, 1, ref a, offs, offs+n1);
+                    ablas.rmatrixsyrk(n2, n1, -1.0, a, offs, offs+n1, 1, 1.0, ref a, offs+n1, offs+n1, isupper);
+                }
+                else
+                {
+                    ablas.rmatrixrighttrsm(n2, n1, a, offs, offs, isupper, false, 1, ref a, offs+n1, offs);
+                    ablas.rmatrixsyrk(n2, n1, -1.0, a, offs+n1, offs, 0, 1.0, ref a, offs+n1, offs+n1, isupper);
+                }
+                result = spdmatrixcholeskyrec(ref a, offs+n1, n2, isupper, ref tmp);
+                if( !result )
+                {
+                    return result;
+                }
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        Recurrent complex LU subroutine.
+        Never call it directly.
+
+          -- ALGLIB routine --
+             04.01.2010
+             Bochkanov Sergey
+        *************************************************************************/
+        private static void cmatrixluprec(ref complex[,] a,
+            int offs,
+            int m,
+            int n,
+            ref int[] pivots,
+            ref complex[] tmp)
+        {
+            int i = 0;
+            int m1 = 0;
+            int m2 = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            
+            //
+            // Kernel case
+            //
+            if( Math.Min(m, n)<=ablas.ablascomplexblocksize(a) )
+            {
+                cmatrixlup2(ref a, offs, m, n, ref pivots, ref tmp);
+                return;
+            }
+            
+            //
+            // Preliminary step, make N>=M
+            //
+            //     ( A1 )
+            // A = (    ), where A1 is square
+            //     ( A2 )
+            //
+            // Factorize A1, update A2
+            //
+            if( m>n )
+            {
+                cmatrixluprec(ref a, offs, n, n, ref pivots, ref tmp);
+                for(i=0; i<=n-1; i++)
+                {
+                    i1_ = (offs+n) - (0);
+                    for(i_=0; i_<=m-n-1;i_++)
+                    {
+                        tmp[i_] = a[i_+i1_,offs+i];
+                    }
+                    for(i_=offs+n; i_<=offs+m-1;i_++)
+                    {
+                        a[i_,offs+i] = a[i_,pivots[offs+i]];
+                    }
+                    i1_ = (0) - (offs+n);
+                    for(i_=offs+n; i_<=offs+m-1;i_++)
+                    {
+                        a[i_,pivots[offs+i]] = tmp[i_+i1_];
+                    }
+                }
+                ablas.cmatrixrighttrsm(m-n, n, a, offs, offs, true, true, 0, ref a, offs+n, offs);
+                return;
+            }
+            
+            //
+            // Non-kernel case
+            //
+            ablas.ablascomplexsplitlength(a, m, ref m1, ref m2);
+            cmatrixluprec(ref a, offs, m1, n, ref pivots, ref tmp);
+            if( m2>0 )
+            {
+                for(i=0; i<=m1-1; i++)
+                {
+                    if( offs+i!=pivots[offs+i] )
+                    {
+                        i1_ = (offs+m1) - (0);
+                        for(i_=0; i_<=m2-1;i_++)
+                        {
+                            tmp[i_] = a[i_+i1_,offs+i];
+                        }
+                        for(i_=offs+m1; i_<=offs+m-1;i_++)
+                        {
+                            a[i_,offs+i] = a[i_,pivots[offs+i]];
+                        }
+                        i1_ = (0) - (offs+m1);
+                        for(i_=offs+m1; i_<=offs+m-1;i_++)
+                        {
+                            a[i_,pivots[offs+i]] = tmp[i_+i1_];
+                        }
+                    }
+                }
+                ablas.cmatrixrighttrsm(m2, m1, a, offs, offs, true, true, 0, ref a, offs+m1, offs);
+                ablas.cmatrixgemm(m-m1, n-m1, m1, -1.0, a, offs+m1, offs, 0, a, offs, offs+m1, 0, 1.0, ref a, offs+m1, offs+m1);
+                cmatrixluprec(ref a, offs+m1, m-m1, n-m1, ref pivots, ref tmp);
+                for(i=0; i<=m2-1; i++)
+                {
+                    if( offs+m1+i!=pivots[offs+m1+i] )
+                    {
+                        i1_ = (offs) - (0);
+                        for(i_=0; i_<=m1-1;i_++)
+                        {
+                            tmp[i_] = a[i_+i1_,offs+m1+i];
+                        }
+                        for(i_=offs; i_<=offs+m1-1;i_++)
+                        {
+                            a[i_,offs+m1+i] = a[i_,pivots[offs+m1+i]];
+                        }
+                        i1_ = (0) - (offs);
+                        for(i_=offs; i_<=offs+m1-1;i_++)
+                        {
+                            a[i_,pivots[offs+m1+i]] = tmp[i_+i1_];
+                        }
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Recurrent real LU subroutine.
+        Never call it directly.
+
+          -- ALGLIB routine --
+             04.01.2010
+             Bochkanov Sergey
+        *************************************************************************/
+        private static void rmatrixluprec(ref double[,] a,
+            int offs,
+            int m,
+            int n,
+            ref int[] pivots,
+            ref double[] tmp)
+        {
+            int i = 0;
+            int m1 = 0;
+            int m2 = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            
+            //
+            // Kernel case
+            //
+            if( Math.Min(m, n)<=ablas.ablasblocksize(a) )
+            {
+                rmatrixlup2(ref a, offs, m, n, ref pivots, ref tmp);
+                return;
+            }
+            
+            //
+            // Preliminary step, make N>=M
+            //
+            //     ( A1 )
+            // A = (    ), where A1 is square
+            //     ( A2 )
+            //
+            // Factorize A1, update A2
+            //
+            if( m>n )
+            {
+                rmatrixluprec(ref a, offs, n, n, ref pivots, ref tmp);
+                for(i=0; i<=n-1; i++)
+                {
+                    if( offs+i!=pivots[offs+i] )
+                    {
+                        i1_ = (offs+n) - (0);
+                        for(i_=0; i_<=m-n-1;i_++)
+                        {
+                            tmp[i_] = a[i_+i1_,offs+i];
+                        }
+                        for(i_=offs+n; i_<=offs+m-1;i_++)
+                        {
+                            a[i_,offs+i] = a[i_,pivots[offs+i]];
+                        }
+                        i1_ = (0) - (offs+n);
+                        for(i_=offs+n; i_<=offs+m-1;i_++)
+                        {
+                            a[i_,pivots[offs+i]] = tmp[i_+i1_];
+                        }
+                    }
+                }
+                ablas.rmatrixrighttrsm(m-n, n, a, offs, offs, true, true, 0, ref a, offs+n, offs);
+                return;
+            }
+            
+            //
+            // Non-kernel case
+            //
+            ablas.ablassplitlength(a, m, ref m1, ref m2);
+            rmatrixluprec(ref a, offs, m1, n, ref pivots, ref tmp);
+            if( m2>0 )
+            {
+                for(i=0; i<=m1-1; i++)
+                {
+                    if( offs+i!=pivots[offs+i] )
+                    {
+                        i1_ = (offs+m1) - (0);
+                        for(i_=0; i_<=m2-1;i_++)
+                        {
+                            tmp[i_] = a[i_+i1_,offs+i];
+                        }
+                        for(i_=offs+m1; i_<=offs+m-1;i_++)
+                        {
+                            a[i_,offs+i] = a[i_,pivots[offs+i]];
+                        }
+                        i1_ = (0) - (offs+m1);
+                        for(i_=offs+m1; i_<=offs+m-1;i_++)
+                        {
+                            a[i_,pivots[offs+i]] = tmp[i_+i1_];
+                        }
+                    }
+                }
+                ablas.rmatrixrighttrsm(m2, m1, a, offs, offs, true, true, 0, ref a, offs+m1, offs);
+                ablas.rmatrixgemm(m-m1, n-m1, m1, -1.0, a, offs+m1, offs, 0, a, offs, offs+m1, 0, 1.0, ref a, offs+m1, offs+m1);
+                rmatrixluprec(ref a, offs+m1, m-m1, n-m1, ref pivots, ref tmp);
+                for(i=0; i<=m2-1; i++)
+                {
+                    if( offs+m1+i!=pivots[offs+m1+i] )
+                    {
+                        i1_ = (offs) - (0);
+                        for(i_=0; i_<=m1-1;i_++)
+                        {
+                            tmp[i_] = a[i_+i1_,offs+m1+i];
+                        }
+                        for(i_=offs; i_<=offs+m1-1;i_++)
+                        {
+                            a[i_,offs+m1+i] = a[i_,pivots[offs+m1+i]];
+                        }
+                        i1_ = (0) - (offs);
+                        for(i_=offs; i_<=offs+m1-1;i_++)
+                        {
+                            a[i_,pivots[offs+m1+i]] = tmp[i_+i1_];
+                        }
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Recurrent complex LU subroutine.
+        Never call it directly.
+
+          -- ALGLIB routine --
+             04.01.2010
+             Bochkanov Sergey
+        *************************************************************************/
+        private static void cmatrixplurec(ref complex[,] a,
+            int offs,
+            int m,
+            int n,
+            ref int[] pivots,
+            ref complex[] tmp)
+        {
+            int i = 0;
+            int n1 = 0;
+            int n2 = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            
+            //
+            // Kernel case
+            //
+            if( Math.Min(m, n)<=ablas.ablascomplexblocksize(a) )
+            {
+                cmatrixplu2(ref a, offs, m, n, ref pivots, ref tmp);
+                return;
+            }
+            
+            //
+            // Preliminary step, make M>=N.
+            //
+            // A = (A1 A2), where A1 is square
+            // Factorize A1, update A2
+            //
+            if( n>m )
+            {
+                cmatrixplurec(ref a, offs, m, m, ref pivots, ref tmp);
+                for(i=0; i<=m-1; i++)
+                {
+                    i1_ = (offs+m) - (0);
+                    for(i_=0; i_<=n-m-1;i_++)
+                    {
+                        tmp[i_] = a[offs+i,i_+i1_];
+                    }
+                    for(i_=offs+m; i_<=offs+n-1;i_++)
+                    {
+                        a[offs+i,i_] = a[pivots[offs+i],i_];
+                    }
+                    i1_ = (0) - (offs+m);
+                    for(i_=offs+m; i_<=offs+n-1;i_++)
+                    {
+                        a[pivots[offs+i],i_] = tmp[i_+i1_];
+                    }
+                }
+                ablas.cmatrixlefttrsm(m, n-m, a, offs, offs, false, true, 0, ref a, offs, offs+m);
+                return;
+            }
+            
+            //
+            // Non-kernel case
+            //
+            ablas.ablascomplexsplitlength(a, n, ref n1, ref n2);
+            cmatrixplurec(ref a, offs, m, n1, ref pivots, ref tmp);
+            if( n2>0 )
+            {
+                for(i=0; i<=n1-1; i++)
+                {
+                    if( offs+i!=pivots[offs+i] )
+                    {
+                        i1_ = (offs+n1) - (0);
+                        for(i_=0; i_<=n2-1;i_++)
+                        {
+                            tmp[i_] = a[offs+i,i_+i1_];
+                        }
+                        for(i_=offs+n1; i_<=offs+n-1;i_++)
+                        {
+                            a[offs+i,i_] = a[pivots[offs+i],i_];
+                        }
+                        i1_ = (0) - (offs+n1);
+                        for(i_=offs+n1; i_<=offs+n-1;i_++)
+                        {
+                            a[pivots[offs+i],i_] = tmp[i_+i1_];
+                        }
+                    }
+                }
+                ablas.cmatrixlefttrsm(n1, n2, a, offs, offs, false, true, 0, ref a, offs, offs+n1);
+                ablas.cmatrixgemm(m-n1, n-n1, n1, -1.0, a, offs+n1, offs, 0, a, offs, offs+n1, 0, 1.0, ref a, offs+n1, offs+n1);
+                cmatrixplurec(ref a, offs+n1, m-n1, n-n1, ref pivots, ref tmp);
+                for(i=0; i<=n2-1; i++)
+                {
+                    if( offs+n1+i!=pivots[offs+n1+i] )
+                    {
+                        i1_ = (offs) - (0);
+                        for(i_=0; i_<=n1-1;i_++)
+                        {
+                            tmp[i_] = a[offs+n1+i,i_+i1_];
+                        }
+                        for(i_=offs; i_<=offs+n1-1;i_++)
+                        {
+                            a[offs+n1+i,i_] = a[pivots[offs+n1+i],i_];
+                        }
+                        i1_ = (0) - (offs);
+                        for(i_=offs; i_<=offs+n1-1;i_++)
+                        {
+                            a[pivots[offs+n1+i],i_] = tmp[i_+i1_];
+                        }
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Recurrent real LU subroutine.
+        Never call it directly.
+
+          -- ALGLIB routine --
+             04.01.2010
+             Bochkanov Sergey
+        *************************************************************************/
+        private static void rmatrixplurec(ref double[,] a,
+            int offs,
+            int m,
+            int n,
+            ref int[] pivots,
+            ref double[] tmp)
+        {
+            int i = 0;
+            int n1 = 0;
+            int n2 = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            
+            //
+            // Kernel case
+            //
+            if( Math.Min(m, n)<=ablas.ablasblocksize(a) )
+            {
+                rmatrixplu2(ref a, offs, m, n, ref pivots, ref tmp);
+                return;
+            }
+            
+            //
+            // Preliminary step, make M>=N.
+            //
+            // A = (A1 A2), where A1 is square
+            // Factorize A1, update A2
+            //
+            if( n>m )
+            {
+                rmatrixplurec(ref a, offs, m, m, ref pivots, ref tmp);
+                for(i=0; i<=m-1; i++)
+                {
+                    i1_ = (offs+m) - (0);
+                    for(i_=0; i_<=n-m-1;i_++)
+                    {
+                        tmp[i_] = a[offs+i,i_+i1_];
+                    }
+                    for(i_=offs+m; i_<=offs+n-1;i_++)
+                    {
+                        a[offs+i,i_] = a[pivots[offs+i],i_];
+                    }
+                    i1_ = (0) - (offs+m);
+                    for(i_=offs+m; i_<=offs+n-1;i_++)
+                    {
+                        a[pivots[offs+i],i_] = tmp[i_+i1_];
+                    }
+                }
+                ablas.rmatrixlefttrsm(m, n-m, a, offs, offs, false, true, 0, ref a, offs, offs+m);
+                return;
+            }
+            
+            //
+            // Non-kernel case
+            //
+            ablas.ablassplitlength(a, n, ref n1, ref n2);
+            rmatrixplurec(ref a, offs, m, n1, ref pivots, ref tmp);
+            if( n2>0 )
+            {
+                for(i=0; i<=n1-1; i++)
+                {
+                    if( offs+i!=pivots[offs+i] )
+                    {
+                        i1_ = (offs+n1) - (0);
+                        for(i_=0; i_<=n2-1;i_++)
+                        {
+                            tmp[i_] = a[offs+i,i_+i1_];
+                        }
+                        for(i_=offs+n1; i_<=offs+n-1;i_++)
+                        {
+                            a[offs+i,i_] = a[pivots[offs+i],i_];
+                        }
+                        i1_ = (0) - (offs+n1);
+                        for(i_=offs+n1; i_<=offs+n-1;i_++)
+                        {
+                            a[pivots[offs+i],i_] = tmp[i_+i1_];
+                        }
+                    }
+                }
+                ablas.rmatrixlefttrsm(n1, n2, a, offs, offs, false, true, 0, ref a, offs, offs+n1);
+                ablas.rmatrixgemm(m-n1, n-n1, n1, -1.0, a, offs+n1, offs, 0, a, offs, offs+n1, 0, 1.0, ref a, offs+n1, offs+n1);
+                rmatrixplurec(ref a, offs+n1, m-n1, n-n1, ref pivots, ref tmp);
+                for(i=0; i<=n2-1; i++)
+                {
+                    if( offs+n1+i!=pivots[offs+n1+i] )
+                    {
+                        i1_ = (offs) - (0);
+                        for(i_=0; i_<=n1-1;i_++)
+                        {
+                            tmp[i_] = a[offs+n1+i,i_+i1_];
+                        }
+                        for(i_=offs; i_<=offs+n1-1;i_++)
+                        {
+                            a[offs+n1+i,i_] = a[pivots[offs+n1+i],i_];
+                        }
+                        i1_ = (0) - (offs);
+                        for(i_=offs; i_<=offs+n1-1;i_++)
+                        {
+                            a[pivots[offs+n1+i],i_] = tmp[i_+i1_];
+                        }
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Complex LUP kernel
+
+          -- ALGLIB routine --
+             10.01.2010
+             Bochkanov Sergey
+        *************************************************************************/
+        private static void cmatrixlup2(ref complex[,] a,
+            int offs,
+            int m,
+            int n,
+            ref int[] pivots,
+            ref complex[] tmp)
+        {
+            int i = 0;
+            int j = 0;
+            int jp = 0;
+            complex s = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            
+            //
+            // Quick return if possible
+            //
+            if( m==0 || n==0 )
+            {
+                return;
+            }
+            
+            //
+            // main cycle
+            //
+            for(j=0; j<=Math.Min(m-1, n-1); j++)
+            {
+                
+                //
+                // Find pivot, swap columns
+                //
+                jp = j;
+                for(i=j+1; i<=n-1; i++)
+                {
+                    if( (double)(math.abscomplex(a[offs+j,offs+i]))>(double)(math.abscomplex(a[offs+j,offs+jp])) )
+                    {
+                        jp = i;
+                    }
+                }
+                pivots[offs+j] = offs+jp;
+                if( jp!=j )
+                {
+                    i1_ = (offs) - (0);
+                    for(i_=0; i_<=m-1;i_++)
+                    {
+                        tmp[i_] = a[i_+i1_,offs+j];
+                    }
+                    for(i_=offs; i_<=offs+m-1;i_++)
+                    {
+                        a[i_,offs+j] = a[i_,offs+jp];
+                    }
+                    i1_ = (0) - (offs);
+                    for(i_=offs; i_<=offs+m-1;i_++)
+                    {
+                        a[i_,offs+jp] = tmp[i_+i1_];
+                    }
+                }
+                
+                //
+                // LU decomposition of 1x(N-J) matrix
+                //
+                if( a[offs+j,offs+j]!=0 && j+1<=n-1 )
+                {
+                    s = 1/a[offs+j,offs+j];
+                    for(i_=offs+j+1; i_<=offs+n-1;i_++)
+                    {
+                        a[offs+j,i_] = s*a[offs+j,i_];
+                    }
+                }
+                
+                //
+                // Update trailing (M-J-1)x(N-J-1) matrix
+                //
+                if( j<Math.Min(m-1, n-1) )
+                {
+                    i1_ = (offs+j+1) - (0);
+                    for(i_=0; i_<=m-j-2;i_++)
+                    {
+                        tmp[i_] = a[i_+i1_,offs+j];
+                    }
+                    i1_ = (offs+j+1) - (m);
+                    for(i_=m; i_<=m+n-j-2;i_++)
+                    {
+                        tmp[i_] = -a[offs+j,i_+i1_];
+                    }
+                    ablas.cmatrixrank1(m-j-1, n-j-1, ref a, offs+j+1, offs+j+1, ref tmp, 0, ref tmp, m);
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Real LUP kernel
+
+          -- ALGLIB routine --
+             10.01.2010
+             Bochkanov Sergey
+        *************************************************************************/
+        private static void rmatrixlup2(ref double[,] a,
+            int offs,
+            int m,
+            int n,
+            ref int[] pivots,
+            ref double[] tmp)
+        {
+            int i = 0;
+            int j = 0;
+            int jp = 0;
+            double s = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            
+            //
+            // Quick return if possible
+            //
+            if( m==0 || n==0 )
+            {
+                return;
+            }
+            
+            //
+            // main cycle
+            //
+            for(j=0; j<=Math.Min(m-1, n-1); j++)
+            {
+                
+                //
+                // Find pivot, swap columns
+                //
+                jp = j;
+                for(i=j+1; i<=n-1; i++)
+                {
+                    if( (double)(Math.Abs(a[offs+j,offs+i]))>(double)(Math.Abs(a[offs+j,offs+jp])) )
+                    {
+                        jp = i;
+                    }
+                }
+                pivots[offs+j] = offs+jp;
+                if( jp!=j )
+                {
+                    i1_ = (offs) - (0);
+                    for(i_=0; i_<=m-1;i_++)
+                    {
+                        tmp[i_] = a[i_+i1_,offs+j];
+                    }
+                    for(i_=offs; i_<=offs+m-1;i_++)
+                    {
+                        a[i_,offs+j] = a[i_,offs+jp];
+                    }
+                    i1_ = (0) - (offs);
+                    for(i_=offs; i_<=offs+m-1;i_++)
+                    {
+                        a[i_,offs+jp] = tmp[i_+i1_];
+                    }
+                }
+                
+                //
+                // LU decomposition of 1x(N-J) matrix
+                //
+                if( (double)(a[offs+j,offs+j])!=(double)(0) && j+1<=n-1 )
+                {
+                    s = 1/a[offs+j,offs+j];
+                    for(i_=offs+j+1; i_<=offs+n-1;i_++)
+                    {
+                        a[offs+j,i_] = s*a[offs+j,i_];
+                    }
+                }
+                
+                //
+                // Update trailing (M-J-1)x(N-J-1) matrix
+                //
+                if( j<Math.Min(m-1, n-1) )
+                {
+                    i1_ = (offs+j+1) - (0);
+                    for(i_=0; i_<=m-j-2;i_++)
+                    {
+                        tmp[i_] = a[i_+i1_,offs+j];
+                    }
+                    i1_ = (offs+j+1) - (m);
+                    for(i_=m; i_<=m+n-j-2;i_++)
+                    {
+                        tmp[i_] = -a[offs+j,i_+i1_];
+                    }
+                    ablas.rmatrixrank1(m-j-1, n-j-1, ref a, offs+j+1, offs+j+1, ref tmp, 0, ref tmp, m);
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Complex PLU kernel
+
+          -- LAPACK routine (version 3.0) --
+             Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd.,
+             Courant Institute, Argonne National Lab, and Rice University
+             June 30, 1992
+        *************************************************************************/
+        private static void cmatrixplu2(ref complex[,] a,
+            int offs,
+            int m,
+            int n,
+            ref int[] pivots,
+            ref complex[] tmp)
+        {
+            int i = 0;
+            int j = 0;
+            int jp = 0;
+            complex s = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            
+            //
+            // Quick return if possible
+            //
+            if( m==0 || n==0 )
+            {
+                return;
+            }
+            for(j=0; j<=Math.Min(m-1, n-1); j++)
+            {
+                
+                //
+                // Find pivot and test for singularity.
+                //
+                jp = j;
+                for(i=j+1; i<=m-1; i++)
+                {
+                    if( (double)(math.abscomplex(a[offs+i,offs+j]))>(double)(math.abscomplex(a[offs+jp,offs+j])) )
+                    {
+                        jp = i;
+                    }
+                }
+                pivots[offs+j] = offs+jp;
+                if( a[offs+jp,offs+j]!=0 )
+                {
+                    
+                    //
+                    //Apply the interchange to rows
+                    //
+                    if( jp!=j )
+                    {
+                        for(i=0; i<=n-1; i++)
+                        {
+                            s = a[offs+j,offs+i];
+                            a[offs+j,offs+i] = a[offs+jp,offs+i];
+                            a[offs+jp,offs+i] = s;
+                        }
+                    }
+                    
+                    //
+                    //Compute elements J+1:M of J-th column.
+                    //
+                    if( j+1<=m-1 )
+                    {
+                        s = 1/a[offs+j,offs+j];
+                        for(i_=offs+j+1; i_<=offs+m-1;i_++)
+                        {
+                            a[i_,offs+j] = s*a[i_,offs+j];
+                        }
+                    }
+                }
+                if( j<Math.Min(m, n)-1 )
+                {
+                    
+                    //
+                    //Update trailing submatrix.
+                    //
+                    i1_ = (offs+j+1) - (0);
+                    for(i_=0; i_<=m-j-2;i_++)
+                    {
+                        tmp[i_] = a[i_+i1_,offs+j];
+                    }
+                    i1_ = (offs+j+1) - (m);
+                    for(i_=m; i_<=m+n-j-2;i_++)
+                    {
+                        tmp[i_] = -a[offs+j,i_+i1_];
+                    }
+                    ablas.cmatrixrank1(m-j-1, n-j-1, ref a, offs+j+1, offs+j+1, ref tmp, 0, ref tmp, m);
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Real PLU kernel
+
+          -- LAPACK routine (version 3.0) --
+             Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd.,
+             Courant Institute, Argonne National Lab, and Rice University
+             June 30, 1992
+        *************************************************************************/
+        private static void rmatrixplu2(ref double[,] a,
+            int offs,
+            int m,
+            int n,
+            ref int[] pivots,
+            ref double[] tmp)
+        {
+            int i = 0;
+            int j = 0;
+            int jp = 0;
+            double s = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            
+            //
+            // Quick return if possible
+            //
+            if( m==0 || n==0 )
+            {
+                return;
+            }
+            for(j=0; j<=Math.Min(m-1, n-1); j++)
+            {
+                
+                //
+                // Find pivot and test for singularity.
+                //
+                jp = j;
+                for(i=j+1; i<=m-1; i++)
+                {
+                    if( (double)(Math.Abs(a[offs+i,offs+j]))>(double)(Math.Abs(a[offs+jp,offs+j])) )
+                    {
+                        jp = i;
+                    }
+                }
+                pivots[offs+j] = offs+jp;
+                if( (double)(a[offs+jp,offs+j])!=(double)(0) )
+                {
+                    
+                    //
+                    //Apply the interchange to rows
+                    //
+                    if( jp!=j )
+                    {
+                        for(i=0; i<=n-1; i++)
+                        {
+                            s = a[offs+j,offs+i];
+                            a[offs+j,offs+i] = a[offs+jp,offs+i];
+                            a[offs+jp,offs+i] = s;
+                        }
+                    }
+                    
+                    //
+                    //Compute elements J+1:M of J-th column.
+                    //
+                    if( j+1<=m-1 )
+                    {
+                        s = 1/a[offs+j,offs+j];
+                        for(i_=offs+j+1; i_<=offs+m-1;i_++)
+                        {
+                            a[i_,offs+j] = s*a[i_,offs+j];
+                        }
+                    }
+                }
+                if( j<Math.Min(m, n)-1 )
+                {
+                    
+                    //
+                    //Update trailing submatrix.
+                    //
+                    i1_ = (offs+j+1) - (0);
+                    for(i_=0; i_<=m-j-2;i_++)
+                    {
+                        tmp[i_] = a[i_+i1_,offs+j];
+                    }
+                    i1_ = (offs+j+1) - (m);
+                    for(i_=m; i_<=m+n-j-2;i_++)
+                    {
+                        tmp[i_] = -a[offs+j,i_+i1_];
+                    }
+                    ablas.rmatrixrank1(m-j-1, n-j-1, ref a, offs+j+1, offs+j+1, ref tmp, 0, ref tmp, m);
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Recursive computational subroutine for HPDMatrixCholesky
+
+          -- ALGLIB routine --
+             15.12.2009
+             Bochkanov Sergey
+        *************************************************************************/
+        private static bool hpdmatrixcholeskyrec(ref complex[,] a,
+            int offs,
+            int n,
+            bool isupper,
+            ref complex[] tmp)
+        {
+            bool result = new bool();
+            int n1 = 0;
+            int n2 = 0;
+
+            
+            //
+            // check N
+            //
+            if( n<1 )
+            {
+                result = false;
+                return result;
+            }
+            
+            //
+            // Prepare buffer
+            //
+            if( alglib.ap.len(tmp)<2*n )
+            {
+                tmp = new complex[2*n];
+            }
+            
+            //
+            // special cases
+            //
+            if( n==1 )
+            {
+                if( (double)(a[offs,offs].x)>(double)(0) )
+                {
+                    a[offs,offs] = Math.Sqrt(a[offs,offs].x);
+                    result = true;
+                }
+                else
+                {
+                    result = false;
+                }
+                return result;
+            }
+            if( n<=ablas.ablascomplexblocksize(a) )
+            {
+                result = hpdmatrixcholesky2(ref a, offs, n, isupper, ref tmp);
+                return result;
+            }
+            
+            //
+            // general case: split task in cache-oblivious manner
+            //
+            result = true;
+            ablas.ablascomplexsplitlength(a, n, ref n1, ref n2);
+            result = hpdmatrixcholeskyrec(ref a, offs, n1, isupper, ref tmp);
+            if( !result )
+            {
+                return result;
+            }
+            if( n2>0 )
+            {
+                if( isupper )
+                {
+                    ablas.cmatrixlefttrsm(n1, n2, a, offs, offs, isupper, false, 2, ref a, offs, offs+n1);
+                    ablas.cmatrixsyrk(n2, n1, -1.0, a, offs, offs+n1, 2, 1.0, ref a, offs+n1, offs+n1, isupper);
+                }
+                else
+                {
+                    ablas.cmatrixrighttrsm(n2, n1, a, offs, offs, isupper, false, 2, ref a, offs+n1, offs);
+                    ablas.cmatrixsyrk(n2, n1, -1.0, a, offs+n1, offs, 0, 1.0, ref a, offs+n1, offs+n1, isupper);
+                }
+                result = hpdmatrixcholeskyrec(ref a, offs+n1, n2, isupper, ref tmp);
+                if( !result )
+                {
+                    return result;
+                }
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        Level-2 Hermitian Cholesky subroutine.
+
+          -- LAPACK routine (version 3.0) --
+             Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd.,
+             Courant Institute, Argonne National Lab, and Rice University
+             February 29, 1992
+        *************************************************************************/
+        private static bool hpdmatrixcholesky2(ref complex[,] aaa,
+            int offs,
+            int n,
+            bool isupper,
+            ref complex[] tmp)
+        {
+            bool result = new bool();
+            int i = 0;
+            int j = 0;
+            double ajj = 0;
+            complex v = 0;
+            double r = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            result = true;
+            if( n<0 )
+            {
+                result = false;
+                return result;
+            }
+            
+            //
+            // Quick return if possible
+            //
+            if( n==0 )
+            {
+                return result;
+            }
+            if( isupper )
+            {
+                
+                //
+                // Compute the Cholesky factorization A = U'*U.
+                //
+                for(j=0; j<=n-1; j++)
+                {
+                    
+                    //
+                    // Compute U(J,J) and test for non-positive-definiteness.
+                    //
+                    v = 0.0;
+                    for(i_=offs; i_<=offs+j-1;i_++)
+                    {
+                        v += math.conj(aaa[i_,offs+j])*aaa[i_,offs+j];
+                    }
+                    ajj = (aaa[offs+j,offs+j]-v).x;
+                    if( (double)(ajj)<=(double)(0) )
+                    {
+                        aaa[offs+j,offs+j] = ajj;
+                        result = false;
+                        return result;
+                    }
+                    ajj = Math.Sqrt(ajj);
+                    aaa[offs+j,offs+j] = ajj;
+                    
+                    //
+                    // Compute elements J+1:N-1 of row J.
+                    //
+                    if( j<n-1 )
+                    {
+                        if( j>0 )
+                        {
+                            i1_ = (offs) - (0);
+                            for(i_=0; i_<=j-1;i_++)
+                            {
+                                tmp[i_] = -math.conj(aaa[i_+i1_,offs+j]);
+                            }
+                            ablas.cmatrixmv(n-j-1, j, aaa, offs, offs+j+1, 1, tmp, 0, ref tmp, n);
+                            i1_ = (n) - (offs+j+1);
+                            for(i_=offs+j+1; i_<=offs+n-1;i_++)
+                            {
+                                aaa[offs+j,i_] = aaa[offs+j,i_] + tmp[i_+i1_];
+                            }
+                        }
+                        r = 1/ajj;
+                        for(i_=offs+j+1; i_<=offs+n-1;i_++)
+                        {
+                            aaa[offs+j,i_] = r*aaa[offs+j,i_];
+                        }
+                    }
+                }
+            }
+            else
+            {
+                
+                //
+                // Compute the Cholesky factorization A = L*L'.
+                //
+                for(j=0; j<=n-1; j++)
+                {
+                    
+                    //
+                    // Compute L(J+1,J+1) and test for non-positive-definiteness.
+                    //
+                    v = 0.0;
+                    for(i_=offs; i_<=offs+j-1;i_++)
+                    {
+                        v += math.conj(aaa[offs+j,i_])*aaa[offs+j,i_];
+                    }
+                    ajj = (aaa[offs+j,offs+j]-v).x;
+                    if( (double)(ajj)<=(double)(0) )
+                    {
+                        aaa[offs+j,offs+j] = ajj;
+                        result = false;
+                        return result;
+                    }
+                    ajj = Math.Sqrt(ajj);
+                    aaa[offs+j,offs+j] = ajj;
+                    
+                    //
+                    // Compute elements J+1:N of column J.
+                    //
+                    if( j<n-1 )
+                    {
+                        if( j>0 )
+                        {
+                            i1_ = (offs) - (0);
+                            for(i_=0; i_<=j-1;i_++)
+                            {
+                                tmp[i_] = math.conj(aaa[offs+j,i_+i1_]);
+                            }
+                            ablas.cmatrixmv(n-j-1, j, aaa, offs+j+1, offs, 0, tmp, 0, ref tmp, n);
+                            for(i=0; i<=n-j-2; i++)
+                            {
+                                aaa[offs+j+1+i,offs+j] = (aaa[offs+j+1+i,offs+j]-tmp[n+i])/ajj;
+                            }
+                        }
+                        else
+                        {
+                            for(i=0; i<=n-j-2; i++)
+                            {
+                                aaa[offs+j+1+i,offs+j] = aaa[offs+j+1+i,offs+j]/ajj;
+                            }
+                        }
+                    }
+                }
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        Level-2 Cholesky subroutine
+
+          -- LAPACK routine (version 3.0) --
+             Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd.,
+             Courant Institute, Argonne National Lab, and Rice University
+             February 29, 1992
+        *************************************************************************/
+        private static bool spdmatrixcholesky2(ref double[,] aaa,
+            int offs,
+            int n,
+            bool isupper,
+            ref double[] tmp)
+        {
+            bool result = new bool();
+            int i = 0;
+            int j = 0;
+            double ajj = 0;
+            double v = 0;
+            double r = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            result = true;
+            if( n<0 )
+            {
+                result = false;
+                return result;
+            }
+            
+            //
+            // Quick return if possible
+            //
+            if( n==0 )
+            {
+                return result;
+            }
+            if( isupper )
+            {
+                
+                //
+                // Compute the Cholesky factorization A = U'*U.
+                //
+                for(j=0; j<=n-1; j++)
+                {
+                    
+                    //
+                    // Compute U(J,J) and test for non-positive-definiteness.
+                    //
+                    v = 0.0;
+                    for(i_=offs; i_<=offs+j-1;i_++)
+                    {
+                        v += aaa[i_,offs+j]*aaa[i_,offs+j];
+                    }
+                    ajj = aaa[offs+j,offs+j]-v;
+                    if( (double)(ajj)<=(double)(0) )
+                    {
+                        aaa[offs+j,offs+j] = ajj;
+                        result = false;
+                        return result;
+                    }
+                    ajj = Math.Sqrt(ajj);
+                    aaa[offs+j,offs+j] = ajj;
+                    
+                    //
+                    // Compute elements J+1:N-1 of row J.
+                    //
+                    if( j<n-1 )
+                    {
+                        if( j>0 )
+                        {
+                            i1_ = (offs) - (0);
+                            for(i_=0; i_<=j-1;i_++)
+                            {
+                                tmp[i_] = -aaa[i_+i1_,offs+j];
+                            }
+                            ablas.rmatrixmv(n-j-1, j, aaa, offs, offs+j+1, 1, tmp, 0, ref tmp, n);
+                            i1_ = (n) - (offs+j+1);
+                            for(i_=offs+j+1; i_<=offs+n-1;i_++)
+                            {
+                                aaa[offs+j,i_] = aaa[offs+j,i_] + tmp[i_+i1_];
+                            }
+                        }
+                        r = 1/ajj;
+                        for(i_=offs+j+1; i_<=offs+n-1;i_++)
+                        {
+                            aaa[offs+j,i_] = r*aaa[offs+j,i_];
+                        }
+                    }
+                }
+            }
+            else
+            {
+                
+                //
+                // Compute the Cholesky factorization A = L*L'.
+                //
+                for(j=0; j<=n-1; j++)
+                {
+                    
+                    //
+                    // Compute L(J+1,J+1) and test for non-positive-definiteness.
+                    //
+                    v = 0.0;
+                    for(i_=offs; i_<=offs+j-1;i_++)
+                    {
+                        v += aaa[offs+j,i_]*aaa[offs+j,i_];
+                    }
+                    ajj = aaa[offs+j,offs+j]-v;
+                    if( (double)(ajj)<=(double)(0) )
+                    {
+                        aaa[offs+j,offs+j] = ajj;
+                        result = false;
+                        return result;
+                    }
+                    ajj = Math.Sqrt(ajj);
+                    aaa[offs+j,offs+j] = ajj;
+                    
+                    //
+                    // Compute elements J+1:N of column J.
+                    //
+                    if( j<n-1 )
+                    {
+                        if( j>0 )
+                        {
+                            i1_ = (offs) - (0);
+                            for(i_=0; i_<=j-1;i_++)
+                            {
+                                tmp[i_] = aaa[offs+j,i_+i1_];
+                            }
+                            ablas.rmatrixmv(n-j-1, j, aaa, offs+j+1, offs, 0, tmp, 0, ref tmp, n);
+                            for(i=0; i<=n-j-2; i++)
+                            {
+                                aaa[offs+j+1+i,offs+j] = (aaa[offs+j+1+i,offs+j]-tmp[n+i])/ajj;
+                            }
+                        }
+                        else
+                        {
+                            for(i=0; i<=n-j-2; i++)
+                            {
+                                aaa[offs+j+1+i,offs+j] = aaa[offs+j+1+i,offs+j]/ajj;
+                            }
+                        }
+                    }
+                }
+            }
+            return result;
+        }
+
+
+    }
+    public class rcond
+    {
+        /*************************************************************************
+        Estimate of a matrix condition number (1-norm)
+
+        The algorithm calculates a lower bound of the condition number. In this case,
+        the algorithm does not return a lower bound of the condition number, but an
+        inverse number (to avoid an overflow in case of a singular matrix).
+
+        Input parameters:
+            A   -   matrix. Array whose indexes range within [0..N-1, 0..N-1].
+            N   -   size of matrix A.
+
+        Result: 1/LowerBound(cond(A))
+
+        NOTE:
+            if k(A) is very large, then matrix is  assumed  degenerate,  k(A)=INF,
+            0.0 is returned in such cases.
+        *************************************************************************/
+        public static double rmatrixrcond1(double[,] a,
+            int n)
+        {
+            double result = 0;
+            int i = 0;
+            int j = 0;
+            double v = 0;
+            double nrm = 0;
+            int[] pivots = new int[0];
+            double[] t = new double[0];
+
+            a = (double[,])a.Clone();
+
+            alglib.ap.assert(n>=1, "RMatrixRCond1: N<1!");
+            t = new double[n];
+            for(i=0; i<=n-1; i++)
+            {
+                t[i] = 0;
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                for(j=0; j<=n-1; j++)
+                {
+                    t[j] = t[j]+Math.Abs(a[i,j]);
+                }
+            }
+            nrm = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                nrm = Math.Max(nrm, t[i]);
+            }
+            trfac.rmatrixlu(ref a, n, n, ref pivots);
+            rmatrixrcondluinternal(a, n, true, true, nrm, ref v);
+            result = v;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Estimate of a matrix condition number (infinity-norm).
+
+        The algorithm calculates a lower bound of the condition number. In this case,
+        the algorithm does not return a lower bound of the condition number, but an
+        inverse number (to avoid an overflow in case of a singular matrix).
+
+        Input parameters:
+            A   -   matrix. Array whose indexes range within [0..N-1, 0..N-1].
+            N   -   size of matrix A.
+
+        Result: 1/LowerBound(cond(A))
+
+        NOTE:
+            if k(A) is very large, then matrix is  assumed  degenerate,  k(A)=INF,
+            0.0 is returned in such cases.
+        *************************************************************************/
+        public static double rmatrixrcondinf(double[,] a,
+            int n)
+        {
+            double result = 0;
+            int i = 0;
+            int j = 0;
+            double v = 0;
+            double nrm = 0;
+            int[] pivots = new int[0];
+
+            a = (double[,])a.Clone();
+
+            alglib.ap.assert(n>=1, "RMatrixRCondInf: N<1!");
+            nrm = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                v = 0;
+                for(j=0; j<=n-1; j++)
+                {
+                    v = v+Math.Abs(a[i,j]);
+                }
+                nrm = Math.Max(nrm, v);
+            }
+            trfac.rmatrixlu(ref a, n, n, ref pivots);
+            rmatrixrcondluinternal(a, n, false, true, nrm, ref v);
+            result = v;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Condition number estimate of a symmetric positive definite matrix.
+
+        The algorithm calculates a lower bound of the condition number. In this case,
+        the algorithm does not return a lower bound of the condition number, but an
+        inverse number (to avoid an overflow in case of a singular matrix).
+
+        It should be noted that 1-norm and inf-norm of condition numbers of symmetric
+        matrices are equal, so the algorithm doesn't take into account the
+        differences between these types of norms.
+
+        Input parameters:
+            A       -   symmetric positive definite matrix which is given by its
+                        upper or lower triangle depending on the value of
+                        IsUpper. Array with elements [0..N-1, 0..N-1].
+            N       -   size of matrix A.
+            IsUpper -   storage format.
+
+        Result:
+            1/LowerBound(cond(A)), if matrix A is positive definite,
+           -1, if matrix A is not positive definite, and its condition number
+            could not be found by this algorithm.
+
+        NOTE:
+            if k(A) is very large, then matrix is  assumed  degenerate,  k(A)=INF,
+            0.0 is returned in such cases.
+        *************************************************************************/
+        public static double spdmatrixrcond(double[,] a,
+            int n,
+            bool isupper)
+        {
+            double result = 0;
+            int i = 0;
+            int j = 0;
+            int j1 = 0;
+            int j2 = 0;
+            double v = 0;
+            double nrm = 0;
+            double[] t = new double[0];
+
+            a = (double[,])a.Clone();
+
+            t = new double[n];
+            for(i=0; i<=n-1; i++)
+            {
+                t[i] = 0;
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                if( isupper )
+                {
+                    j1 = i;
+                    j2 = n-1;
+                }
+                else
+                {
+                    j1 = 0;
+                    j2 = i;
+                }
+                for(j=j1; j<=j2; j++)
+                {
+                    if( i==j )
+                    {
+                        t[i] = t[i]+Math.Abs(a[i,i]);
+                    }
+                    else
+                    {
+                        t[i] = t[i]+Math.Abs(a[i,j]);
+                        t[j] = t[j]+Math.Abs(a[i,j]);
+                    }
+                }
+            }
+            nrm = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                nrm = Math.Max(nrm, t[i]);
+            }
+            if( trfac.spdmatrixcholesky(ref a, n, isupper) )
+            {
+                spdmatrixrcondcholeskyinternal(a, n, isupper, true, nrm, ref v);
+                result = v;
+            }
+            else
+            {
+                result = -1;
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        Triangular matrix: estimate of a condition number (1-norm)
+
+        The algorithm calculates a lower bound of the condition number. In this case,
+        the algorithm does not return a lower bound of the condition number, but an
+        inverse number (to avoid an overflow in case of a singular matrix).
+
+        Input parameters:
+            A       -   matrix. Array[0..N-1, 0..N-1].
+            N       -   size of A.
+            IsUpper -   True, if the matrix is upper triangular.
+            IsUnit  -   True, if the matrix has a unit diagonal.
+
+        Result: 1/LowerBound(cond(A))
+
+        NOTE:
+            if k(A) is very large, then matrix is  assumed  degenerate,  k(A)=INF,
+            0.0 is returned in such cases.
+        *************************************************************************/
+        public static double rmatrixtrrcond1(double[,] a,
+            int n,
+            bool isupper,
+            bool isunit)
+        {
+            double result = 0;
+            int i = 0;
+            int j = 0;
+            double v = 0;
+            double nrm = 0;
+            int[] pivots = new int[0];
+            double[] t = new double[0];
+            int j1 = 0;
+            int j2 = 0;
+
+            alglib.ap.assert(n>=1, "RMatrixTRRCond1: N<1!");
+            t = new double[n];
+            for(i=0; i<=n-1; i++)
+            {
+                t[i] = 0;
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                if( isupper )
+                {
+                    j1 = i+1;
+                    j2 = n-1;
+                }
+                else
+                {
+                    j1 = 0;
+                    j2 = i-1;
+                }
+                for(j=j1; j<=j2; j++)
+                {
+                    t[j] = t[j]+Math.Abs(a[i,j]);
+                }
+                if( isunit )
+                {
+                    t[i] = t[i]+1;
+                }
+                else
+                {
+                    t[i] = t[i]+Math.Abs(a[i,i]);
+                }
+            }
+            nrm = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                nrm = Math.Max(nrm, t[i]);
+            }
+            rmatrixrcondtrinternal(a, n, isupper, isunit, true, nrm, ref v);
+            result = v;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Triangular matrix: estimate of a matrix condition number (infinity-norm).
+
+        The algorithm calculates a lower bound of the condition number. In this case,
+        the algorithm does not return a lower bound of the condition number, but an
+        inverse number (to avoid an overflow in case of a singular matrix).
+
+        Input parameters:
+            A   -   matrix. Array whose indexes range within [0..N-1, 0..N-1].
+            N   -   size of matrix A.
+            IsUpper -   True, if the matrix is upper triangular.
+            IsUnit  -   True, if the matrix has a unit diagonal.
+
+        Result: 1/LowerBound(cond(A))
+
+        NOTE:
+            if k(A) is very large, then matrix is  assumed  degenerate,  k(A)=INF,
+            0.0 is returned in such cases.
+        *************************************************************************/
+        public static double rmatrixtrrcondinf(double[,] a,
+            int n,
+            bool isupper,
+            bool isunit)
+        {
+            double result = 0;
+            int i = 0;
+            int j = 0;
+            double v = 0;
+            double nrm = 0;
+            int[] pivots = new int[0];
+            int j1 = 0;
+            int j2 = 0;
+
+            alglib.ap.assert(n>=1, "RMatrixTRRCondInf: N<1!");
+            nrm = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                if( isupper )
+                {
+                    j1 = i+1;
+                    j2 = n-1;
+                }
+                else
+                {
+                    j1 = 0;
+                    j2 = i-1;
+                }
+                v = 0;
+                for(j=j1; j<=j2; j++)
+                {
+                    v = v+Math.Abs(a[i,j]);
+                }
+                if( isunit )
+                {
+                    v = v+1;
+                }
+                else
+                {
+                    v = v+Math.Abs(a[i,i]);
+                }
+                nrm = Math.Max(nrm, v);
+            }
+            rmatrixrcondtrinternal(a, n, isupper, isunit, false, nrm, ref v);
+            result = v;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Condition number estimate of a Hermitian positive definite matrix.
+
+        The algorithm calculates a lower bound of the condition number. In this case,
+        the algorithm does not return a lower bound of the condition number, but an
+        inverse number (to avoid an overflow in case of a singular matrix).
+
+        It should be noted that 1-norm and inf-norm of condition numbers of symmetric
+        matrices are equal, so the algorithm doesn't take into account the
+        differences between these types of norms.
+
+        Input parameters:
+            A       -   Hermitian positive definite matrix which is given by its
+                        upper or lower triangle depending on the value of
+                        IsUpper. Array with elements [0..N-1, 0..N-1].
+            N       -   size of matrix A.
+            IsUpper -   storage format.
+
+        Result:
+            1/LowerBound(cond(A)), if matrix A is positive definite,
+           -1, if matrix A is not positive definite, and its condition number
+            could not be found by this algorithm.
+
+        NOTE:
+            if k(A) is very large, then matrix is  assumed  degenerate,  k(A)=INF,
+            0.0 is returned in such cases.
+        *************************************************************************/
+        public static double hpdmatrixrcond(complex[,] a,
+            int n,
+            bool isupper)
+        {
+            double result = 0;
+            int i = 0;
+            int j = 0;
+            int j1 = 0;
+            int j2 = 0;
+            double v = 0;
+            double nrm = 0;
+            double[] t = new double[0];
+
+            a = (complex[,])a.Clone();
+
+            t = new double[n];
+            for(i=0; i<=n-1; i++)
+            {
+                t[i] = 0;
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                if( isupper )
+                {
+                    j1 = i;
+                    j2 = n-1;
+                }
+                else
+                {
+                    j1 = 0;
+                    j2 = i;
+                }
+                for(j=j1; j<=j2; j++)
+                {
+                    if( i==j )
+                    {
+                        t[i] = t[i]+math.abscomplex(a[i,i]);
+                    }
+                    else
+                    {
+                        t[i] = t[i]+math.abscomplex(a[i,j]);
+                        t[j] = t[j]+math.abscomplex(a[i,j]);
+                    }
+                }
+            }
+            nrm = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                nrm = Math.Max(nrm, t[i]);
+            }
+            if( trfac.hpdmatrixcholesky(ref a, n, isupper) )
+            {
+                hpdmatrixrcondcholeskyinternal(a, n, isupper, true, nrm, ref v);
+                result = v;
+            }
+            else
+            {
+                result = -1;
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        Estimate of a matrix condition number (1-norm)
+
+        The algorithm calculates a lower bound of the condition number. In this case,
+        the algorithm does not return a lower bound of the condition number, but an
+        inverse number (to avoid an overflow in case of a singular matrix).
+
+        Input parameters:
+            A   -   matrix. Array whose indexes range within [0..N-1, 0..N-1].
+            N   -   size of matrix A.
+
+        Result: 1/LowerBound(cond(A))
+
+        NOTE:
+            if k(A) is very large, then matrix is  assumed  degenerate,  k(A)=INF,
+            0.0 is returned in such cases.
+        *************************************************************************/
+        public static double cmatrixrcond1(complex[,] a,
+            int n)
+        {
+            double result = 0;
+            int i = 0;
+            int j = 0;
+            double v = 0;
+            double nrm = 0;
+            int[] pivots = new int[0];
+            double[] t = new double[0];
+
+            a = (complex[,])a.Clone();
+
+            alglib.ap.assert(n>=1, "CMatrixRCond1: N<1!");
+            t = new double[n];
+            for(i=0; i<=n-1; i++)
+            {
+                t[i] = 0;
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                for(j=0; j<=n-1; j++)
+                {
+                    t[j] = t[j]+math.abscomplex(a[i,j]);
+                }
+            }
+            nrm = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                nrm = Math.Max(nrm, t[i]);
+            }
+            trfac.cmatrixlu(ref a, n, n, ref pivots);
+            cmatrixrcondluinternal(a, n, true, true, nrm, ref v);
+            result = v;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Estimate of a matrix condition number (infinity-norm).
+
+        The algorithm calculates a lower bound of the condition number. In this case,
+        the algorithm does not return a lower bound of the condition number, but an
+        inverse number (to avoid an overflow in case of a singular matrix).
+
+        Input parameters:
+            A   -   matrix. Array whose indexes range within [0..N-1, 0..N-1].
+            N   -   size of matrix A.
+
+        Result: 1/LowerBound(cond(A))
+
+        NOTE:
+            if k(A) is very large, then matrix is  assumed  degenerate,  k(A)=INF,
+            0.0 is returned in such cases.
+        *************************************************************************/
+        public static double cmatrixrcondinf(complex[,] a,
+            int n)
+        {
+            double result = 0;
+            int i = 0;
+            int j = 0;
+            double v = 0;
+            double nrm = 0;
+            int[] pivots = new int[0];
+
+            a = (complex[,])a.Clone();
+
+            alglib.ap.assert(n>=1, "CMatrixRCondInf: N<1!");
+            nrm = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                v = 0;
+                for(j=0; j<=n-1; j++)
+                {
+                    v = v+math.abscomplex(a[i,j]);
+                }
+                nrm = Math.Max(nrm, v);
+            }
+            trfac.cmatrixlu(ref a, n, n, ref pivots);
+            cmatrixrcondluinternal(a, n, false, true, nrm, ref v);
+            result = v;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Estimate of the condition number of a matrix given by its LU decomposition (1-norm)
+
+        The algorithm calculates a lower bound of the condition number. In this case,
+        the algorithm does not return a lower bound of the condition number, but an
+        inverse number (to avoid an overflow in case of a singular matrix).
+
+        Input parameters:
+            LUA         -   LU decomposition of a matrix in compact form. Output of
+                            the RMatrixLU subroutine.
+            N           -   size of matrix A.
+
+        Result: 1/LowerBound(cond(A))
+
+        NOTE:
+            if k(A) is very large, then matrix is  assumed  degenerate,  k(A)=INF,
+            0.0 is returned in such cases.
+        *************************************************************************/
+        public static double rmatrixlurcond1(double[,] lua,
+            int n)
+        {
+            double result = 0;
+            double v = 0;
+
+            rmatrixrcondluinternal(lua, n, true, false, 0, ref v);
+            result = v;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Estimate of the condition number of a matrix given by its LU decomposition
+        (infinity norm).
+
+        The algorithm calculates a lower bound of the condition number. In this case,
+        the algorithm does not return a lower bound of the condition number, but an
+        inverse number (to avoid an overflow in case of a singular matrix).
+
+        Input parameters:
+            LUA     -   LU decomposition of a matrix in compact form. Output of
+                        the RMatrixLU subroutine.
+            N       -   size of matrix A.
+
+        Result: 1/LowerBound(cond(A))
+
+        NOTE:
+            if k(A) is very large, then matrix is  assumed  degenerate,  k(A)=INF,
+            0.0 is returned in such cases.
+        *************************************************************************/
+        public static double rmatrixlurcondinf(double[,] lua,
+            int n)
+        {
+            double result = 0;
+            double v = 0;
+
+            rmatrixrcondluinternal(lua, n, false, false, 0, ref v);
+            result = v;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Condition number estimate of a symmetric positive definite matrix given by
+        Cholesky decomposition.
+
+        The algorithm calculates a lower bound of the condition number. In this
+        case, the algorithm does not return a lower bound of the condition number,
+        but an inverse number (to avoid an overflow in case of a singular matrix).
+
+        It should be noted that 1-norm and inf-norm condition numbers of symmetric
+        matrices are equal, so the algorithm doesn't take into account the
+        differences between these types of norms.
+
+        Input parameters:
+            CD  - Cholesky decomposition of matrix A,
+                  output of SMatrixCholesky subroutine.
+            N   - size of matrix A.
+
+        Result: 1/LowerBound(cond(A))
+
+        NOTE:
+            if k(A) is very large, then matrix is  assumed  degenerate,  k(A)=INF,
+            0.0 is returned in such cases.
+        *************************************************************************/
+        public static double spdmatrixcholeskyrcond(double[,] a,
+            int n,
+            bool isupper)
+        {
+            double result = 0;
+            double v = 0;
+
+            spdmatrixrcondcholeskyinternal(a, n, isupper, false, 0, ref v);
+            result = v;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Condition number estimate of a Hermitian positive definite matrix given by
+        Cholesky decomposition.
+
+        The algorithm calculates a lower bound of the condition number. In this
+        case, the algorithm does not return a lower bound of the condition number,
+        but an inverse number (to avoid an overflow in case of a singular matrix).
+
+        It should be noted that 1-norm and inf-norm condition numbers of symmetric
+        matrices are equal, so the algorithm doesn't take into account the
+        differences between these types of norms.
+
+        Input parameters:
+            CD  - Cholesky decomposition of matrix A,
+                  output of SMatrixCholesky subroutine.
+            N   - size of matrix A.
+
+        Result: 1/LowerBound(cond(A))
+
+        NOTE:
+            if k(A) is very large, then matrix is  assumed  degenerate,  k(A)=INF,
+            0.0 is returned in such cases.
+        *************************************************************************/
+        public static double hpdmatrixcholeskyrcond(complex[,] a,
+            int n,
+            bool isupper)
+        {
+            double result = 0;
+            double v = 0;
+
+            hpdmatrixrcondcholeskyinternal(a, n, isupper, false, 0, ref v);
+            result = v;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Estimate of the condition number of a matrix given by its LU decomposition (1-norm)
+
+        The algorithm calculates a lower bound of the condition number. In this case,
+        the algorithm does not return a lower bound of the condition number, but an
+        inverse number (to avoid an overflow in case of a singular matrix).
+
+        Input parameters:
+            LUA         -   LU decomposition of a matrix in compact form. Output of
+                            the CMatrixLU subroutine.
+            N           -   size of matrix A.
+
+        Result: 1/LowerBound(cond(A))
+
+        NOTE:
+            if k(A) is very large, then matrix is  assumed  degenerate,  k(A)=INF,
+            0.0 is returned in such cases.
+        *************************************************************************/
+        public static double cmatrixlurcond1(complex[,] lua,
+            int n)
+        {
+            double result = 0;
+            double v = 0;
+
+            alglib.ap.assert(n>=1, "CMatrixLURCond1: N<1!");
+            cmatrixrcondluinternal(lua, n, true, false, 0.0, ref v);
+            result = v;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Estimate of the condition number of a matrix given by its LU decomposition
+        (infinity norm).
+
+        The algorithm calculates a lower bound of the condition number. In this case,
+        the algorithm does not return a lower bound of the condition number, but an
+        inverse number (to avoid an overflow in case of a singular matrix).
+
+        Input parameters:
+            LUA     -   LU decomposition of a matrix in compact form. Output of
+                        the CMatrixLU subroutine.
+            N       -   size of matrix A.
+
+        Result: 1/LowerBound(cond(A))
+
+        NOTE:
+            if k(A) is very large, then matrix is  assumed  degenerate,  k(A)=INF,
+            0.0 is returned in such cases.
+        *************************************************************************/
+        public static double cmatrixlurcondinf(complex[,] lua,
+            int n)
+        {
+            double result = 0;
+            double v = 0;
+
+            alglib.ap.assert(n>=1, "CMatrixLURCondInf: N<1!");
+            cmatrixrcondluinternal(lua, n, false, false, 0.0, ref v);
+            result = v;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Triangular matrix: estimate of a condition number (1-norm)
+
+        The algorithm calculates a lower bound of the condition number. In this case,
+        the algorithm does not return a lower bound of the condition number, but an
+        inverse number (to avoid an overflow in case of a singular matrix).
+
+        Input parameters:
+            A       -   matrix. Array[0..N-1, 0..N-1].
+            N       -   size of A.
+            IsUpper -   True, if the matrix is upper triangular.
+            IsUnit  -   True, if the matrix has a unit diagonal.
+
+        Result: 1/LowerBound(cond(A))
+
+        NOTE:
+            if k(A) is very large, then matrix is  assumed  degenerate,  k(A)=INF,
+            0.0 is returned in such cases.
+        *************************************************************************/
+        public static double cmatrixtrrcond1(complex[,] a,
+            int n,
+            bool isupper,
+            bool isunit)
+        {
+            double result = 0;
+            int i = 0;
+            int j = 0;
+            double v = 0;
+            double nrm = 0;
+            int[] pivots = new int[0];
+            double[] t = new double[0];
+            int j1 = 0;
+            int j2 = 0;
+
+            alglib.ap.assert(n>=1, "RMatrixTRRCond1: N<1!");
+            t = new double[n];
+            for(i=0; i<=n-1; i++)
+            {
+                t[i] = 0;
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                if( isupper )
+                {
+                    j1 = i+1;
+                    j2 = n-1;
+                }
+                else
+                {
+                    j1 = 0;
+                    j2 = i-1;
+                }
+                for(j=j1; j<=j2; j++)
+                {
+                    t[j] = t[j]+math.abscomplex(a[i,j]);
+                }
+                if( isunit )
+                {
+                    t[i] = t[i]+1;
+                }
+                else
+                {
+                    t[i] = t[i]+math.abscomplex(a[i,i]);
+                }
+            }
+            nrm = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                nrm = Math.Max(nrm, t[i]);
+            }
+            cmatrixrcondtrinternal(a, n, isupper, isunit, true, nrm, ref v);
+            result = v;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Triangular matrix: estimate of a matrix condition number (infinity-norm).
+
+        The algorithm calculates a lower bound of the condition number. In this case,
+        the algorithm does not return a lower bound of the condition number, but an
+        inverse number (to avoid an overflow in case of a singular matrix).
+
+        Input parameters:
+            A   -   matrix. Array whose indexes range within [0..N-1, 0..N-1].
+            N   -   size of matrix A.
+            IsUpper -   True, if the matrix is upper triangular.
+            IsUnit  -   True, if the matrix has a unit diagonal.
+
+        Result: 1/LowerBound(cond(A))
+
+        NOTE:
+            if k(A) is very large, then matrix is  assumed  degenerate,  k(A)=INF,
+            0.0 is returned in such cases.
+        *************************************************************************/
+        public static double cmatrixtrrcondinf(complex[,] a,
+            int n,
+            bool isupper,
+            bool isunit)
+        {
+            double result = 0;
+            int i = 0;
+            int j = 0;
+            double v = 0;
+            double nrm = 0;
+            int[] pivots = new int[0];
+            int j1 = 0;
+            int j2 = 0;
+
+            alglib.ap.assert(n>=1, "RMatrixTRRCondInf: N<1!");
+            nrm = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                if( isupper )
+                {
+                    j1 = i+1;
+                    j2 = n-1;
+                }
+                else
+                {
+                    j1 = 0;
+                    j2 = i-1;
+                }
+                v = 0;
+                for(j=j1; j<=j2; j++)
+                {
+                    v = v+math.abscomplex(a[i,j]);
+                }
+                if( isunit )
+                {
+                    v = v+1;
+                }
+                else
+                {
+                    v = v+math.abscomplex(a[i,i]);
+                }
+                nrm = Math.Max(nrm, v);
+            }
+            cmatrixrcondtrinternal(a, n, isupper, isunit, false, nrm, ref v);
+            result = v;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Threshold for rcond: matrices with condition number beyond this  threshold
+        are considered singular.
+
+        Threshold must be far enough from underflow, at least Sqr(Threshold)  must
+        be greater than underflow.
+        *************************************************************************/
+        public static double rcondthreshold()
+        {
+            double result = 0;
+
+            result = Math.Sqrt(Math.Sqrt(math.minrealnumber));
+            return result;
+        }
+
+
+        /*************************************************************************
+        Internal subroutine for condition number estimation
+
+          -- LAPACK routine (version 3.0) --
+             Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd.,
+             Courant Institute, Argonne National Lab, and Rice University
+             February 29, 1992
+        *************************************************************************/
+        private static void rmatrixrcondtrinternal(double[,] a,
+            int n,
+            bool isupper,
+            bool isunit,
+            bool onenorm,
+            double anorm,
+            ref double rc)
+        {
+            double[] ex = new double[0];
+            double[] ev = new double[0];
+            int[] iwork = new int[0];
+            double[] tmp = new double[0];
+            int i = 0;
+            int j = 0;
+            int kase = 0;
+            int kase1 = 0;
+            int j1 = 0;
+            int j2 = 0;
+            double ainvnm = 0;
+            double maxgrowth = 0;
+            double s = 0;
+            bool mupper = new bool();
+            bool mtrans = new bool();
+            bool munit = new bool();
+
+            rc = 0;
+
+            
+            //
+            // RC=0 if something happens
+            //
+            rc = 0;
+            
+            //
+            // init
+            //
+            if( onenorm )
+            {
+                kase1 = 1;
+            }
+            else
+            {
+                kase1 = 2;
+            }
+            mupper = true;
+            mtrans = true;
+            munit = true;
+            iwork = new int[n+1];
+            tmp = new double[n];
+            
+            //
+            // prepare parameters for triangular solver
+            //
+            maxgrowth = 1/rcondthreshold();
+            s = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                if( isupper )
+                {
+                    j1 = i+1;
+                    j2 = n-1;
+                }
+                else
+                {
+                    j1 = 0;
+                    j2 = i-1;
+                }
+                for(j=j1; j<=j2; j++)
+                {
+                    s = Math.Max(s, Math.Abs(a[i,j]));
+                }
+                if( isunit )
+                {
+                    s = Math.Max(s, 1);
+                }
+                else
+                {
+                    s = Math.Max(s, Math.Abs(a[i,i]));
+                }
+            }
+            if( (double)(s)==(double)(0) )
+            {
+                s = 1;
+            }
+            s = 1/s;
+            
+            //
+            // Scale according to S
+            //
+            anorm = anorm*s;
+            
+            //
+            // Quick return if possible
+            // We assume that ANORM<>0 after this block
+            //
+            if( (double)(anorm)==(double)(0) )
+            {
+                return;
+            }
+            if( n==1 )
+            {
+                rc = 1;
+                return;
+            }
+            
+            //
+            // Estimate the norm of inv(A).
+            //
+            ainvnm = 0;
+            kase = 0;
+            while( true )
+            {
+                rmatrixestimatenorm(n, ref ev, ref ex, ref iwork, ref ainvnm, ref kase);
+                if( kase==0 )
+                {
+                    break;
+                }
+                
+                //
+                // from 1-based array to 0-based
+                //
+                for(i=0; i<=n-1; i++)
+                {
+                    ex[i] = ex[i+1];
+                }
+                
+                //
+                // multiply by inv(A) or inv(A')
+                //
+                if( kase==kase1 )
+                {
+                    
+                    //
+                    // multiply by inv(A)
+                    //
+                    if( !safesolve.rmatrixscaledtrsafesolve(a, s, n, ref ex, isupper, 0, isunit, maxgrowth) )
+                    {
+                        return;
+                    }
+                }
+                else
+                {
+                    
+                    //
+                    // multiply by inv(A')
+                    //
+                    if( !safesolve.rmatrixscaledtrsafesolve(a, s, n, ref ex, isupper, 1, isunit, maxgrowth) )
+                    {
+                        return;
+                    }
+                }
+                
+                //
+                // from 0-based array to 1-based
+                //
+                for(i=n-1; i>=0; i--)
+                {
+                    ex[i+1] = ex[i];
+                }
+            }
+            
+            //
+            // Compute the estimate of the reciprocal condition number.
+            //
+            if( (double)(ainvnm)!=(double)(0) )
+            {
+                rc = 1/ainvnm;
+                rc = rc/anorm;
+                if( (double)(rc)<(double)(rcondthreshold()) )
+                {
+                    rc = 0;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Condition number estimation
+
+          -- LAPACK routine (version 3.0) --
+             Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd.,
+             Courant Institute, Argonne National Lab, and Rice University
+             March 31, 1993
+        *************************************************************************/
+        private static void cmatrixrcondtrinternal(complex[,] a,
+            int n,
+            bool isupper,
+            bool isunit,
+            bool onenorm,
+            double anorm,
+            ref double rc)
+        {
+            complex[] ex = new complex[0];
+            complex[] cwork2 = new complex[0];
+            complex[] cwork3 = new complex[0];
+            complex[] cwork4 = new complex[0];
+            int[] isave = new int[0];
+            double[] rsave = new double[0];
+            int kase = 0;
+            int kase1 = 0;
+            double ainvnm = 0;
+            int i = 0;
+            int j = 0;
+            int j1 = 0;
+            int j2 = 0;
+            double s = 0;
+            double maxgrowth = 0;
+
+            rc = 0;
+
+            
+            //
+            // RC=0 if something happens
+            //
+            rc = 0;
+            
+            //
+            // init
+            //
+            if( n<=0 )
+            {
+                return;
+            }
+            if( n==0 )
+            {
+                rc = 1;
+                return;
+            }
+            cwork2 = new complex[n+1];
+            
+            //
+            // prepare parameters for triangular solver
+            //
+            maxgrowth = 1/rcondthreshold();
+            s = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                if( isupper )
+                {
+                    j1 = i+1;
+                    j2 = n-1;
+                }
+                else
+                {
+                    j1 = 0;
+                    j2 = i-1;
+                }
+                for(j=j1; j<=j2; j++)
+                {
+                    s = Math.Max(s, math.abscomplex(a[i,j]));
+                }
+                if( isunit )
+                {
+                    s = Math.Max(s, 1);
+                }
+                else
+                {
+                    s = Math.Max(s, math.abscomplex(a[i,i]));
+                }
+            }
+            if( (double)(s)==(double)(0) )
+            {
+                s = 1;
+            }
+            s = 1/s;
+            
+            //
+            // Scale according to S
+            //
+            anorm = anorm*s;
+            
+            //
+            // Quick return if possible
+            //
+            if( (double)(anorm)==(double)(0) )
+            {
+                return;
+            }
+            
+            //
+            // Estimate the norm of inv(A).
+            //
+            ainvnm = 0;
+            if( onenorm )
+            {
+                kase1 = 1;
+            }
+            else
+            {
+                kase1 = 2;
+            }
+            kase = 0;
+            while( true )
+            {
+                cmatrixestimatenorm(n, ref cwork4, ref ex, ref ainvnm, ref kase, ref isave, ref rsave);
+                if( kase==0 )
+                {
+                    break;
+                }
+                
+                //
+                // From 1-based to 0-based
+                //
+                for(i=0; i<=n-1; i++)
+                {
+                    ex[i] = ex[i+1];
+                }
+                
+                //
+                // multiply by inv(A) or inv(A')
+                //
+                if( kase==kase1 )
+                {
+                    
+                    //
+                    // multiply by inv(A)
+                    //
+                    if( !safesolve.cmatrixscaledtrsafesolve(a, s, n, ref ex, isupper, 0, isunit, maxgrowth) )
+                    {
+                        return;
+                    }
+                }
+                else
+                {
+                    
+                    //
+                    // multiply by inv(A')
+                    //
+                    if( !safesolve.cmatrixscaledtrsafesolve(a, s, n, ref ex, isupper, 2, isunit, maxgrowth) )
+                    {
+                        return;
+                    }
+                }
+                
+                //
+                // from 0-based to 1-based
+                //
+                for(i=n-1; i>=0; i--)
+                {
+                    ex[i+1] = ex[i];
+                }
+            }
+            
+            //
+            // Compute the estimate of the reciprocal condition number.
+            //
+            if( (double)(ainvnm)!=(double)(0) )
+            {
+                rc = 1/ainvnm;
+                rc = rc/anorm;
+                if( (double)(rc)<(double)(rcondthreshold()) )
+                {
+                    rc = 0;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Internal subroutine for condition number estimation
+
+          -- LAPACK routine (version 3.0) --
+             Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd.,
+             Courant Institute, Argonne National Lab, and Rice University
+             February 29, 1992
+        *************************************************************************/
+        private static void spdmatrixrcondcholeskyinternal(double[,] cha,
+            int n,
+            bool isupper,
+            bool isnormprovided,
+            double anorm,
+            ref double rc)
+        {
+            int i = 0;
+            int j = 0;
+            int kase = 0;
+            double ainvnm = 0;
+            double[] ex = new double[0];
+            double[] ev = new double[0];
+            double[] tmp = new double[0];
+            int[] iwork = new int[0];
+            double sa = 0;
+            double v = 0;
+            double maxgrowth = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            rc = 0;
+
+            alglib.ap.assert(n>=1);
+            tmp = new double[n];
+            
+            //
+            // RC=0 if something happens
+            //
+            rc = 0;
+            
+            //
+            // prepare parameters for triangular solver
+            //
+            maxgrowth = 1/rcondthreshold();
+            sa = 0;
+            if( isupper )
+            {
+                for(i=0; i<=n-1; i++)
+                {
+                    for(j=i; j<=n-1; j++)
+                    {
+                        sa = Math.Max(sa, math.abscomplex(cha[i,j]));
+                    }
+                }
+            }
+            else
+            {
+                for(i=0; i<=n-1; i++)
+                {
+                    for(j=0; j<=i; j++)
+                    {
+                        sa = Math.Max(sa, math.abscomplex(cha[i,j]));
+                    }
+                }
+            }
+            if( (double)(sa)==(double)(0) )
+            {
+                sa = 1;
+            }
+            sa = 1/sa;
+            
+            //
+            // Estimate the norm of A.
+            //
+            if( !isnormprovided )
+            {
+                kase = 0;
+                anorm = 0;
+                while( true )
+                {
+                    rmatrixestimatenorm(n, ref ev, ref ex, ref iwork, ref anorm, ref kase);
+                    if( kase==0 )
+                    {
+                        break;
+                    }
+                    if( isupper )
+                    {
+                        
+                        //
+                        // Multiply by U
+                        //
+                        for(i=1; i<=n; i++)
+                        {
+                            i1_ = (i)-(i-1);
+                            v = 0.0;
+                            for(i_=i-1; i_<=n-1;i_++)
+                            {
+                                v += cha[i-1,i_]*ex[i_+i1_];
+                            }
+                            ex[i] = v;
+                        }
+                        for(i_=1; i_<=n;i_++)
+                        {
+                            ex[i_] = sa*ex[i_];
+                        }
+                        
+                        //
+                        // Multiply by U'
+                        //
+                        for(i=0; i<=n-1; i++)
+                        {
+                            tmp[i] = 0;
+                        }
+                        for(i=0; i<=n-1; i++)
+                        {
+                            v = ex[i+1];
+                            for(i_=i; i_<=n-1;i_++)
+                            {
+                                tmp[i_] = tmp[i_] + v*cha[i,i_];
+                            }
+                        }
+                        i1_ = (0) - (1);
+                        for(i_=1; i_<=n;i_++)
+                        {
+                            ex[i_] = tmp[i_+i1_];
+                        }
+                        for(i_=1; i_<=n;i_++)
+                        {
+                            ex[i_] = sa*ex[i_];
+                        }
+                    }
+                    else
+                    {
+                        
+                        //
+                        // Multiply by L'
+                        //
+                        for(i=0; i<=n-1; i++)
+                        {
+                            tmp[i] = 0;
+                        }
+                        for(i=0; i<=n-1; i++)
+                        {
+                            v = ex[i+1];
+                            for(i_=0; i_<=i;i_++)
+                            {
+                                tmp[i_] = tmp[i_] + v*cha[i,i_];
+                            }
+                        }
+                        i1_ = (0) - (1);
+                        for(i_=1; i_<=n;i_++)
+                        {
+                            ex[i_] = tmp[i_+i1_];
+                        }
+                        for(i_=1; i_<=n;i_++)
+                        {
+                            ex[i_] = sa*ex[i_];
+                        }
+                        
+                        //
+                        // Multiply by L
+                        //
+                        for(i=n; i>=1; i--)
+                        {
+                            i1_ = (1)-(0);
+                            v = 0.0;
+                            for(i_=0; i_<=i-1;i_++)
+                            {
+                                v += cha[i-1,i_]*ex[i_+i1_];
+                            }
+                            ex[i] = v;
+                        }
+                        for(i_=1; i_<=n;i_++)
+                        {
+                            ex[i_] = sa*ex[i_];
+                        }
+                    }
+                }
+            }
+            
+            //
+            // Quick return if possible
+            //
+            if( (double)(anorm)==(double)(0) )
+            {
+                return;
+            }
+            if( n==1 )
+            {
+                rc = 1;
+                return;
+            }
+            
+            //
+            // Estimate the 1-norm of inv(A).
+            //
+            kase = 0;
+            while( true )
+            {
+                rmatrixestimatenorm(n, ref ev, ref ex, ref iwork, ref ainvnm, ref kase);
+                if( kase==0 )
+                {
+                    break;
+                }
+                for(i=0; i<=n-1; i++)
+                {
+                    ex[i] = ex[i+1];
+                }
+                if( isupper )
+                {
+                    
+                    //
+                    // Multiply by inv(U').
+                    //
+                    if( !safesolve.rmatrixscaledtrsafesolve(cha, sa, n, ref ex, isupper, 1, false, maxgrowth) )
+                    {
+                        return;
+                    }
+                    
+                    //
+                    // Multiply by inv(U).
+                    //
+                    if( !safesolve.rmatrixscaledtrsafesolve(cha, sa, n, ref ex, isupper, 0, false, maxgrowth) )
+                    {
+                        return;
+                    }
+                }
+                else
+                {
+                    
+                    //
+                    // Multiply by inv(L).
+                    //
+                    if( !safesolve.rmatrixscaledtrsafesolve(cha, sa, n, ref ex, isupper, 0, false, maxgrowth) )
+                    {
+                        return;
+                    }
+                    
+                    //
+                    // Multiply by inv(L').
+                    //
+                    if( !safesolve.rmatrixscaledtrsafesolve(cha, sa, n, ref ex, isupper, 1, false, maxgrowth) )
+                    {
+                        return;
+                    }
+                }
+                for(i=n-1; i>=0; i--)
+                {
+                    ex[i+1] = ex[i];
+                }
+            }
+            
+            //
+            // Compute the estimate of the reciprocal condition number.
+            //
+            if( (double)(ainvnm)!=(double)(0) )
+            {
+                v = 1/ainvnm;
+                rc = v/anorm;
+                if( (double)(rc)<(double)(rcondthreshold()) )
+                {
+                    rc = 0;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Internal subroutine for condition number estimation
+
+          -- LAPACK routine (version 3.0) --
+             Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd.,
+             Courant Institute, Argonne National Lab, and Rice University
+             February 29, 1992
+        *************************************************************************/
+        private static void hpdmatrixrcondcholeskyinternal(complex[,] cha,
+            int n,
+            bool isupper,
+            bool isnormprovided,
+            double anorm,
+            ref double rc)
+        {
+            int[] isave = new int[0];
+            double[] rsave = new double[0];
+            complex[] ex = new complex[0];
+            complex[] ev = new complex[0];
+            complex[] tmp = new complex[0];
+            int kase = 0;
+            double ainvnm = 0;
+            complex v = 0;
+            int i = 0;
+            int j = 0;
+            double sa = 0;
+            double maxgrowth = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            rc = 0;
+
+            alglib.ap.assert(n>=1);
+            tmp = new complex[n];
+            
+            //
+            // RC=0 if something happens
+            //
+            rc = 0;
+            
+            //
+            // prepare parameters for triangular solver
+            //
+            maxgrowth = 1/rcondthreshold();
+            sa = 0;
+            if( isupper )
+            {
+                for(i=0; i<=n-1; i++)
+                {
+                    for(j=i; j<=n-1; j++)
+                    {
+                        sa = Math.Max(sa, math.abscomplex(cha[i,j]));
+                    }
+                }
+            }
+            else
+            {
+                for(i=0; i<=n-1; i++)
+                {
+                    for(j=0; j<=i; j++)
+                    {
+                        sa = Math.Max(sa, math.abscomplex(cha[i,j]));
+                    }
+                }
+            }
+            if( (double)(sa)==(double)(0) )
+            {
+                sa = 1;
+            }
+            sa = 1/sa;
+            
+            //
+            // Estimate the norm of A
+            //
+            if( !isnormprovided )
+            {
+                anorm = 0;
+                kase = 0;
+                while( true )
+                {
+                    cmatrixestimatenorm(n, ref ev, ref ex, ref anorm, ref kase, ref isave, ref rsave);
+                    if( kase==0 )
+                    {
+                        break;
+                    }
+                    if( isupper )
+                    {
+                        
+                        //
+                        // Multiply by U
+                        //
+                        for(i=1; i<=n; i++)
+                        {
+                            i1_ = (i)-(i-1);
+                            v = 0.0;
+                            for(i_=i-1; i_<=n-1;i_++)
+                            {
+                                v += cha[i-1,i_]*ex[i_+i1_];
+                            }
+                            ex[i] = v;
+                        }
+                        for(i_=1; i_<=n;i_++)
+                        {
+                            ex[i_] = sa*ex[i_];
+                        }
+                        
+                        //
+                        // Multiply by U'
+                        //
+                        for(i=0; i<=n-1; i++)
+                        {
+                            tmp[i] = 0;
+                        }
+                        for(i=0; i<=n-1; i++)
+                        {
+                            v = ex[i+1];
+                            for(i_=i; i_<=n-1;i_++)
+                            {
+                                tmp[i_] = tmp[i_] + v*math.conj(cha[i,i_]);
+                            }
+                        }
+                        i1_ = (0) - (1);
+                        for(i_=1; i_<=n;i_++)
+                        {
+                            ex[i_] = tmp[i_+i1_];
+                        }
+                        for(i_=1; i_<=n;i_++)
+                        {
+                            ex[i_] = sa*ex[i_];
+                        }
+                    }
+                    else
+                    {
+                        
+                        //
+                        // Multiply by L'
+                        //
+                        for(i=0; i<=n-1; i++)
+                        {
+                            tmp[i] = 0;
+                        }
+                        for(i=0; i<=n-1; i++)
+                        {
+                            v = ex[i+1];
+                            for(i_=0; i_<=i;i_++)
+                            {
+                                tmp[i_] = tmp[i_] + v*math.conj(cha[i,i_]);
+                            }
+                        }
+                        i1_ = (0) - (1);
+                        for(i_=1; i_<=n;i_++)
+                        {
+                            ex[i_] = tmp[i_+i1_];
+                        }
+                        for(i_=1; i_<=n;i_++)
+                        {
+                            ex[i_] = sa*ex[i_];
+                        }
+                        
+                        //
+                        // Multiply by L
+                        //
+                        for(i=n; i>=1; i--)
+                        {
+                            i1_ = (1)-(0);
+                            v = 0.0;
+                            for(i_=0; i_<=i-1;i_++)
+                            {
+                                v += cha[i-1,i_]*ex[i_+i1_];
+                            }
+                            ex[i] = v;
+                        }
+                        for(i_=1; i_<=n;i_++)
+                        {
+                            ex[i_] = sa*ex[i_];
+                        }
+                    }
+                }
+            }
+            
+            //
+            // Quick return if possible
+            // After this block we assume that ANORM<>0
+            //
+            if( (double)(anorm)==(double)(0) )
+            {
+                return;
+            }
+            if( n==1 )
+            {
+                rc = 1;
+                return;
+            }
+            
+            //
+            // Estimate the norm of inv(A).
+            //
+            ainvnm = 0;
+            kase = 0;
+            while( true )
+            {
+                cmatrixestimatenorm(n, ref ev, ref ex, ref ainvnm, ref kase, ref isave, ref rsave);
+                if( kase==0 )
+                {
+                    break;
+                }
+                for(i=0; i<=n-1; i++)
+                {
+                    ex[i] = ex[i+1];
+                }
+                if( isupper )
+                {
+                    
+                    //
+                    // Multiply by inv(U').
+                    //
+                    if( !safesolve.cmatrixscaledtrsafesolve(cha, sa, n, ref ex, isupper, 2, false, maxgrowth) )
+                    {
+                        return;
+                    }
+                    
+                    //
+                    // Multiply by inv(U).
+                    //
+                    if( !safesolve.cmatrixscaledtrsafesolve(cha, sa, n, ref ex, isupper, 0, false, maxgrowth) )
+                    {
+                        return;
+                    }
+                }
+                else
+                {
+                    
+                    //
+                    // Multiply by inv(L).
+                    //
+                    if( !safesolve.cmatrixscaledtrsafesolve(cha, sa, n, ref ex, isupper, 0, false, maxgrowth) )
+                    {
+                        return;
+                    }
+                    
+                    //
+                    // Multiply by inv(L').
+                    //
+                    if( !safesolve.cmatrixscaledtrsafesolve(cha, sa, n, ref ex, isupper, 2, false, maxgrowth) )
+                    {
+                        return;
+                    }
+                }
+                for(i=n-1; i>=0; i--)
+                {
+                    ex[i+1] = ex[i];
+                }
+            }
+            
+            //
+            // Compute the estimate of the reciprocal condition number.
+            //
+            if( (double)(ainvnm)!=(double)(0) )
+            {
+                rc = 1/ainvnm;
+                rc = rc/anorm;
+                if( (double)(rc)<(double)(rcondthreshold()) )
+                {
+                    rc = 0;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Internal subroutine for condition number estimation
+
+          -- LAPACK routine (version 3.0) --
+             Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd.,
+             Courant Institute, Argonne National Lab, and Rice University
+             February 29, 1992
+        *************************************************************************/
+        private static void rmatrixrcondluinternal(double[,] lua,
+            int n,
+            bool onenorm,
+            bool isanormprovided,
+            double anorm,
+            ref double rc)
+        {
+            double[] ex = new double[0];
+            double[] ev = new double[0];
+            int[] iwork = new int[0];
+            double[] tmp = new double[0];
+            double v = 0;
+            int i = 0;
+            int j = 0;
+            int kase = 0;
+            int kase1 = 0;
+            double ainvnm = 0;
+            double maxgrowth = 0;
+            double su = 0;
+            double sl = 0;
+            bool mupper = new bool();
+            bool mtrans = new bool();
+            bool munit = new bool();
+            int i_ = 0;
+            int i1_ = 0;
+
+            rc = 0;
+
+            
+            //
+            // RC=0 if something happens
+            //
+            rc = 0;
+            
+            //
+            // init
+            //
+            if( onenorm )
+            {
+                kase1 = 1;
+            }
+            else
+            {
+                kase1 = 2;
+            }
+            mupper = true;
+            mtrans = true;
+            munit = true;
+            iwork = new int[n+1];
+            tmp = new double[n];
+            
+            //
+            // prepare parameters for triangular solver
+            //
+            maxgrowth = 1/rcondthreshold();
+            su = 0;
+            sl = 1;
+            for(i=0; i<=n-1; i++)
+            {
+                for(j=0; j<=i-1; j++)
+                {
+                    sl = Math.Max(sl, Math.Abs(lua[i,j]));
+                }
+                for(j=i; j<=n-1; j++)
+                {
+                    su = Math.Max(su, Math.Abs(lua[i,j]));
+                }
+            }
+            if( (double)(su)==(double)(0) )
+            {
+                su = 1;
+            }
+            su = 1/su;
+            sl = 1/sl;
+            
+            //
+            // Estimate the norm of A.
+            //
+            if( !isanormprovided )
+            {
+                kase = 0;
+                anorm = 0;
+                while( true )
+                {
+                    rmatrixestimatenorm(n, ref ev, ref ex, ref iwork, ref anorm, ref kase);
+                    if( kase==0 )
+                    {
+                        break;
+                    }
+                    if( kase==kase1 )
+                    {
+                        
+                        //
+                        // Multiply by U
+                        //
+                        for(i=1; i<=n; i++)
+                        {
+                            i1_ = (i)-(i-1);
+                            v = 0.0;
+                            for(i_=i-1; i_<=n-1;i_++)
+                            {
+                                v += lua[i-1,i_]*ex[i_+i1_];
+                            }
+                            ex[i] = v;
+                        }
+                        
+                        //
+                        // Multiply by L
+                        //
+                        for(i=n; i>=1; i--)
+                        {
+                            if( i>1 )
+                            {
+                                i1_ = (1)-(0);
+                                v = 0.0;
+                                for(i_=0; i_<=i-2;i_++)
+                                {
+                                    v += lua[i-1,i_]*ex[i_+i1_];
+                                }
+                            }
+                            else
+                            {
+                                v = 0;
+                            }
+                            ex[i] = ex[i]+v;
+                        }
+                    }
+                    else
+                    {
+                        
+                        //
+                        // Multiply by L'
+                        //
+                        for(i=0; i<=n-1; i++)
+                        {
+                            tmp[i] = 0;
+                        }
+                        for(i=0; i<=n-1; i++)
+                        {
+                            v = ex[i+1];
+                            if( i>=1 )
+                            {
+                                for(i_=0; i_<=i-1;i_++)
+                                {
+                                    tmp[i_] = tmp[i_] + v*lua[i,i_];
+                                }
+                            }
+                            tmp[i] = tmp[i]+v;
+                        }
+                        i1_ = (0) - (1);
+                        for(i_=1; i_<=n;i_++)
+                        {
+                            ex[i_] = tmp[i_+i1_];
+                        }
+                        
+                        //
+                        // Multiply by U'
+                        //
+                        for(i=0; i<=n-1; i++)
+                        {
+                            tmp[i] = 0;
+                        }
+                        for(i=0; i<=n-1; i++)
+                        {
+                            v = ex[i+1];
+                            for(i_=i; i_<=n-1;i_++)
+                            {
+                                tmp[i_] = tmp[i_] + v*lua[i,i_];
+                            }
+                        }
+                        i1_ = (0) - (1);
+                        for(i_=1; i_<=n;i_++)
+                        {
+                            ex[i_] = tmp[i_+i1_];
+                        }
+                    }
+                }
+            }
+            
+            //
+            // Scale according to SU/SL
+            //
+            anorm = anorm*su*sl;
+            
+            //
+            // Quick return if possible
+            // We assume that ANORM<>0 after this block
+            //
+            if( (double)(anorm)==(double)(0) )
+            {
+                return;
+            }
+            if( n==1 )
+            {
+                rc = 1;
+                return;
+            }
+            
+            //
+            // Estimate the norm of inv(A).
+            //
+            ainvnm = 0;
+            kase = 0;
+            while( true )
+            {
+                rmatrixestimatenorm(n, ref ev, ref ex, ref iwork, ref ainvnm, ref kase);
+                if( kase==0 )
+                {
+                    break;
+                }
+                
+                //
+                // from 1-based array to 0-based
+                //
+                for(i=0; i<=n-1; i++)
+                {
+                    ex[i] = ex[i+1];
+                }
+                
+                //
+                // multiply by inv(A) or inv(A')
+                //
+                if( kase==kase1 )
+                {
+                    
+                    //
+                    // Multiply by inv(L).
+                    //
+                    if( !safesolve.rmatrixscaledtrsafesolve(lua, sl, n, ref ex, !mupper, 0, munit, maxgrowth) )
+                    {
+                        return;
+                    }
+                    
+                    //
+                    // Multiply by inv(U).
+                    //
+                    if( !safesolve.rmatrixscaledtrsafesolve(lua, su, n, ref ex, mupper, 0, !munit, maxgrowth) )
+                    {
+                        return;
+                    }
+                }
+                else
+                {
+                    
+                    //
+                    // Multiply by inv(U').
+                    //
+                    if( !safesolve.rmatrixscaledtrsafesolve(lua, su, n, ref ex, mupper, 1, !munit, maxgrowth) )
+                    {
+                        return;
+                    }
+                    
+                    //
+                    // Multiply by inv(L').
+                    //
+                    if( !safesolve.rmatrixscaledtrsafesolve(lua, sl, n, ref ex, !mupper, 1, munit, maxgrowth) )
+                    {
+                        return;
+                    }
+                }
+                
+                //
+                // from 0-based array to 1-based
+                //
+                for(i=n-1; i>=0; i--)
+                {
+                    ex[i+1] = ex[i];
+                }
+            }
+            
+            //
+            // Compute the estimate of the reciprocal condition number.
+            //
+            if( (double)(ainvnm)!=(double)(0) )
+            {
+                rc = 1/ainvnm;
+                rc = rc/anorm;
+                if( (double)(rc)<(double)(rcondthreshold()) )
+                {
+                    rc = 0;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Condition number estimation
+
+          -- LAPACK routine (version 3.0) --
+             Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd.,
+             Courant Institute, Argonne National Lab, and Rice University
+             March 31, 1993
+        *************************************************************************/
+        private static void cmatrixrcondluinternal(complex[,] lua,
+            int n,
+            bool onenorm,
+            bool isanormprovided,
+            double anorm,
+            ref double rc)
+        {
+            complex[] ex = new complex[0];
+            complex[] cwork2 = new complex[0];
+            complex[] cwork3 = new complex[0];
+            complex[] cwork4 = new complex[0];
+            int[] isave = new int[0];
+            double[] rsave = new double[0];
+            int kase = 0;
+            int kase1 = 0;
+            double ainvnm = 0;
+            complex v = 0;
+            int i = 0;
+            int j = 0;
+            double su = 0;
+            double sl = 0;
+            double maxgrowth = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            rc = 0;
+
+            if( n<=0 )
+            {
+                return;
+            }
+            cwork2 = new complex[n+1];
+            rc = 0;
+            if( n==0 )
+            {
+                rc = 1;
+                return;
+            }
+            
+            //
+            // prepare parameters for triangular solver
+            //
+            maxgrowth = 1/rcondthreshold();
+            su = 0;
+            sl = 1;
+            for(i=0; i<=n-1; i++)
+            {
+                for(j=0; j<=i-1; j++)
+                {
+                    sl = Math.Max(sl, math.abscomplex(lua[i,j]));
+                }
+                for(j=i; j<=n-1; j++)
+                {
+                    su = Math.Max(su, math.abscomplex(lua[i,j]));
+                }
+            }
+            if( (double)(su)==(double)(0) )
+            {
+                su = 1;
+            }
+            su = 1/su;
+            sl = 1/sl;
+            
+            //
+            // Estimate the norm of SU*SL*A.
+            //
+            if( !isanormprovided )
+            {
+                anorm = 0;
+                if( onenorm )
+                {
+                    kase1 = 1;
+                }
+                else
+                {
+                    kase1 = 2;
+                }
+                kase = 0;
+                do
+                {
+                    cmatrixestimatenorm(n, ref cwork4, ref ex, ref anorm, ref kase, ref isave, ref rsave);
+                    if( kase!=0 )
+                    {
+                        if( kase==kase1 )
+                        {
+                            
+                            //
+                            // Multiply by U
+                            //
+                            for(i=1; i<=n; i++)
+                            {
+                                i1_ = (i)-(i-1);
+                                v = 0.0;
+                                for(i_=i-1; i_<=n-1;i_++)
+                                {
+                                    v += lua[i-1,i_]*ex[i_+i1_];
+                                }
+                                ex[i] = v;
+                            }
+                            
+                            //
+                            // Multiply by L
+                            //
+                            for(i=n; i>=1; i--)
+                            {
+                                v = 0;
+                                if( i>1 )
+                                {
+                                    i1_ = (1)-(0);
+                                    v = 0.0;
+                                    for(i_=0; i_<=i-2;i_++)
+                                    {
+                                        v += lua[i-1,i_]*ex[i_+i1_];
+                                    }
+                                }
+                                ex[i] = v+ex[i];
+                            }
+                        }
+                        else
+                        {
+                            
+                            //
+                            // Multiply by L'
+                            //
+                            for(i=1; i<=n; i++)
+                            {
+                                cwork2[i] = 0;
+                            }
+                            for(i=1; i<=n; i++)
+                            {
+                                v = ex[i];
+                                if( i>1 )
+                                {
+                                    i1_ = (0) - (1);
+                                    for(i_=1; i_<=i-1;i_++)
+                                    {
+                                        cwork2[i_] = cwork2[i_] + v*math.conj(lua[i-1,i_+i1_]);
+                                    }
+                                }
+                                cwork2[i] = cwork2[i]+v;
+                            }
+                            
+                            //
+                            // Multiply by U'
+                            //
+                            for(i=1; i<=n; i++)
+                            {
+                                ex[i] = 0;
+                            }
+                            for(i=1; i<=n; i++)
+                            {
+                                v = cwork2[i];
+                                i1_ = (i-1) - (i);
+                                for(i_=i; i_<=n;i_++)
+                                {
+                                    ex[i_] = ex[i_] + v*math.conj(lua[i-1,i_+i1_]);
+                                }
+                            }
+                        }
+                    }
+                }
+                while( kase!=0 );
+            }
+            
+            //
+            // Scale according to SU/SL
+            //
+            anorm = anorm*su*sl;
+            
+            //
+            // Quick return if possible
+            //
+            if( (double)(anorm)==(double)(0) )
+            {
+                return;
+            }
+            
+            //
+            // Estimate the norm of inv(A).
+            //
+            ainvnm = 0;
+            if( onenorm )
+            {
+                kase1 = 1;
+            }
+            else
+            {
+                kase1 = 2;
+            }
+            kase = 0;
+            while( true )
+            {
+                cmatrixestimatenorm(n, ref cwork4, ref ex, ref ainvnm, ref kase, ref isave, ref rsave);
+                if( kase==0 )
+                {
+                    break;
+                }
+                
+                //
+                // From 1-based to 0-based
+                //
+                for(i=0; i<=n-1; i++)
+                {
+                    ex[i] = ex[i+1];
+                }
+                
+                //
+                // multiply by inv(A) or inv(A')
+                //
+                if( kase==kase1 )
+                {
+                    
+                    //
+                    // Multiply by inv(L).
+                    //
+                    if( !safesolve.cmatrixscaledtrsafesolve(lua, sl, n, ref ex, false, 0, true, maxgrowth) )
+                    {
+                        rc = 0;
+                        return;
+                    }
+                    
+                    //
+                    // Multiply by inv(U).
+                    //
+                    if( !safesolve.cmatrixscaledtrsafesolve(lua, su, n, ref ex, true, 0, false, maxgrowth) )
+                    {
+                        rc = 0;
+                        return;
+                    }
+                }
+                else
+                {
+                    
+                    //
+                    // Multiply by inv(U').
+                    //
+                    if( !safesolve.cmatrixscaledtrsafesolve(lua, su, n, ref ex, true, 2, false, maxgrowth) )
+                    {
+                        rc = 0;
+                        return;
+                    }
+                    
+                    //
+                    // Multiply by inv(L').
+                    //
+                    if( !safesolve.cmatrixscaledtrsafesolve(lua, sl, n, ref ex, false, 2, true, maxgrowth) )
+                    {
+                        rc = 0;
+                        return;
+                    }
+                }
+                
+                //
+                // from 0-based to 1-based
+                //
+                for(i=n-1; i>=0; i--)
+                {
+                    ex[i+1] = ex[i];
+                }
+            }
+            
+            //
+            // Compute the estimate of the reciprocal condition number.
+            //
+            if( (double)(ainvnm)!=(double)(0) )
+            {
+                rc = 1/ainvnm;
+                rc = rc/anorm;
+                if( (double)(rc)<(double)(rcondthreshold()) )
+                {
+                    rc = 0;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Internal subroutine for matrix norm estimation
+
+          -- LAPACK auxiliary routine (version 3.0) --
+             Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd.,
+             Courant Institute, Argonne National Lab, and Rice University
+             February 29, 1992
+        *************************************************************************/
+        private static void rmatrixestimatenorm(int n,
+            ref double[] v,
+            ref double[] x,
+            ref int[] isgn,
+            ref double est,
+            ref int kase)
+        {
+            int itmax = 0;
+            int i = 0;
+            double t = 0;
+            bool flg = new bool();
+            int positer = 0;
+            int posj = 0;
+            int posjlast = 0;
+            int posjump = 0;
+            int posaltsgn = 0;
+            int posestold = 0;
+            int postemp = 0;
+            int i_ = 0;
+
+            itmax = 5;
+            posaltsgn = n+1;
+            posestold = n+2;
+            postemp = n+3;
+            positer = n+1;
+            posj = n+2;
+            posjlast = n+3;
+            posjump = n+4;
+            if( kase==0 )
+            {
+                v = new double[n+4];
+                x = new double[n+1];
+                isgn = new int[n+5];
+                t = (double)1/(double)n;
+                for(i=1; i<=n; i++)
+                {
+                    x[i] = t;
+                }
+                kase = 1;
+                isgn[posjump] = 1;
+                return;
+            }
+            
+            //
+            //     ................ ENTRY   (JUMP = 1)
+            //     FIRST ITERATION.  X HAS BEEN OVERWRITTEN BY A*X.
+            //
+            if( isgn[posjump]==1 )
+            {
+                if( n==1 )
+                {
+                    v[1] = x[1];
+                    est = Math.Abs(v[1]);
+                    kase = 0;
+                    return;
+                }
+                est = 0;
+                for(i=1; i<=n; i++)
+                {
+                    est = est+Math.Abs(x[i]);
+                }
+                for(i=1; i<=n; i++)
+                {
+                    if( (double)(x[i])>=(double)(0) )
+                    {
+                        x[i] = 1;
+                    }
+                    else
+                    {
+                        x[i] = -1;
+                    }
+                    isgn[i] = Math.Sign(x[i]);
+                }
+                kase = 2;
+                isgn[posjump] = 2;
+                return;
+            }
+            
+            //
+            //     ................ ENTRY   (JUMP = 2)
+            //     FIRST ITERATION.  X HAS BEEN OVERWRITTEN BY TRANDPOSE(A)*X.
+            //
+            if( isgn[posjump]==2 )
+            {
+                isgn[posj] = 1;
+                for(i=2; i<=n; i++)
+                {
+                    if( (double)(Math.Abs(x[i]))>(double)(Math.Abs(x[isgn[posj]])) )
+                    {
+                        isgn[posj] = i;
+                    }
+                }
+                isgn[positer] = 2;
+                
+                //
+                // MAIN LOOP - ITERATIONS 2,3,...,ITMAX.
+                //
+                for(i=1; i<=n; i++)
+                {
+                    x[i] = 0;
+                }
+                x[isgn[posj]] = 1;
+                kase = 1;
+                isgn[posjump] = 3;
+                return;
+            }
+            
+            //
+            //     ................ ENTRY   (JUMP = 3)
+            //     X HAS BEEN OVERWRITTEN BY A*X.
+            //
+            if( isgn[posjump]==3 )
+            {
+                for(i_=1; i_<=n;i_++)
+                {
+                    v[i_] = x[i_];
+                }
+                v[posestold] = est;
+                est = 0;
+                for(i=1; i<=n; i++)
+                {
+                    est = est+Math.Abs(v[i]);
+                }
+                flg = false;
+                for(i=1; i<=n; i++)
+                {
+                    if( ((double)(x[i])>=(double)(0) && isgn[i]<0) || ((double)(x[i])<(double)(0) && isgn[i]>=0) )
+                    {
+                        flg = true;
+                    }
+                }
+                
+                //
+                // REPEATED SIGN VECTOR DETECTED, HENCE ALGORITHM HAS CONVERGED.
+                // OR MAY BE CYCLING.
+                //
+                if( !flg || (double)(est)<=(double)(v[posestold]) )
+                {
+                    v[posaltsgn] = 1;
+                    for(i=1; i<=n; i++)
+                    {
+                        x[i] = v[posaltsgn]*(1+(double)(i-1)/(double)(n-1));
+                        v[posaltsgn] = -v[posaltsgn];
+                    }
+                    kase = 1;
+                    isgn[posjump] = 5;
+                    return;
+                }
+                for(i=1; i<=n; i++)
+                {
+                    if( (double)(x[i])>=(double)(0) )
+                    {
+                        x[i] = 1;
+                        isgn[i] = 1;
+                    }
+                    else
+                    {
+                        x[i] = -1;
+                        isgn[i] = -1;
+                    }
+                }
+                kase = 2;
+                isgn[posjump] = 4;
+                return;
+            }
+            
+            //
+            //     ................ ENTRY   (JUMP = 4)
+            //     X HAS BEEN OVERWRITTEN BY TRANDPOSE(A)*X.
+            //
+            if( isgn[posjump]==4 )
+            {
+                isgn[posjlast] = isgn[posj];
+                isgn[posj] = 1;
+                for(i=2; i<=n; i++)
+                {
+                    if( (double)(Math.Abs(x[i]))>(double)(Math.Abs(x[isgn[posj]])) )
+                    {
+                        isgn[posj] = i;
+                    }
+                }
+                if( (double)(x[isgn[posjlast]])!=(double)(Math.Abs(x[isgn[posj]])) && isgn[positer]<itmax )
+                {
+                    isgn[positer] = isgn[positer]+1;
+                    for(i=1; i<=n; i++)
+                    {
+                        x[i] = 0;
+                    }
+                    x[isgn[posj]] = 1;
+                    kase = 1;
+                    isgn[posjump] = 3;
+                    return;
+                }
+                
+                //
+                // ITERATION COMPLETE.  FINAL STAGE.
+                //
+                v[posaltsgn] = 1;
+                for(i=1; i<=n; i++)
+                {
+                    x[i] = v[posaltsgn]*(1+(double)(i-1)/(double)(n-1));
+                    v[posaltsgn] = -v[posaltsgn];
+                }
+                kase = 1;
+                isgn[posjump] = 5;
+                return;
+            }
+            
+            //
+            //     ................ ENTRY   (JUMP = 5)
+            //     X HAS BEEN OVERWRITTEN BY A*X.
+            //
+            if( isgn[posjump]==5 )
+            {
+                v[postemp] = 0;
+                for(i=1; i<=n; i++)
+                {
+                    v[postemp] = v[postemp]+Math.Abs(x[i]);
+                }
+                v[postemp] = 2*v[postemp]/(3*n);
+                if( (double)(v[postemp])>(double)(est) )
+                {
+                    for(i_=1; i_<=n;i_++)
+                    {
+                        v[i_] = x[i_];
+                    }
+                    est = v[postemp];
+                }
+                kase = 0;
+                return;
+            }
+        }
+
+
+        private static void cmatrixestimatenorm(int n,
+            ref complex[] v,
+            ref complex[] x,
+            ref double est,
+            ref int kase,
+            ref int[] isave,
+            ref double[] rsave)
+        {
+            int itmax = 0;
+            int i = 0;
+            int iter = 0;
+            int j = 0;
+            int jlast = 0;
+            int jump = 0;
+            double absxi = 0;
+            double altsgn = 0;
+            double estold = 0;
+            double safmin = 0;
+            double temp = 0;
+            int i_ = 0;
+
+            
+            //
+            //Executable Statements ..
+            //
+            itmax = 5;
+            safmin = math.minrealnumber;
+            if( kase==0 )
+            {
+                v = new complex[n+1];
+                x = new complex[n+1];
+                isave = new int[5];
+                rsave = new double[4];
+                for(i=1; i<=n; i++)
+                {
+                    x[i] = (double)1/(double)n;
+                }
+                kase = 1;
+                jump = 1;
+                internalcomplexrcondsaveall(ref isave, ref rsave, ref i, ref iter, ref j, ref jlast, ref jump, ref absxi, ref altsgn, ref estold, ref temp);
+                return;
+            }
+            internalcomplexrcondloadall(ref isave, ref rsave, ref i, ref iter, ref j, ref jlast, ref jump, ref absxi, ref altsgn, ref estold, ref temp);
+            
+            //
+            // ENTRY   (JUMP = 1)
+            // FIRST ITERATION.  X HAS BEEN OVERWRITTEN BY A*X.
+            //
+            if( jump==1 )
+            {
+                if( n==1 )
+                {
+                    v[1] = x[1];
+                    est = math.abscomplex(v[1]);
+                    kase = 0;
+                    internalcomplexrcondsaveall(ref isave, ref rsave, ref i, ref iter, ref j, ref jlast, ref jump, ref absxi, ref altsgn, ref estold, ref temp);
+                    return;
+                }
+                est = internalcomplexrcondscsum1(x, n);
+                for(i=1; i<=n; i++)
+                {
+                    absxi = math.abscomplex(x[i]);
+                    if( (double)(absxi)>(double)(safmin) )
+                    {
+                        x[i] = x[i]/absxi;
+                    }
+                    else
+                    {
+                        x[i] = 1;
+                    }
+                }
+                kase = 2;
+                jump = 2;
+                internalcomplexrcondsaveall(ref isave, ref rsave, ref i, ref iter, ref j, ref jlast, ref jump, ref absxi, ref altsgn, ref estold, ref temp);
+                return;
+            }
+            
+            //
+            // ENTRY   (JUMP = 2)
+            // FIRST ITERATION.  X HAS BEEN OVERWRITTEN BY CTRANS(A)*X.
+            //
+            if( jump==2 )
+            {
+                j = internalcomplexrcondicmax1(x, n);
+                iter = 2;
+                
+                //
+                // MAIN LOOP - ITERATIONS 2,3,...,ITMAX.
+                //
+                for(i=1; i<=n; i++)
+                {
+                    x[i] = 0;
+                }
+                x[j] = 1;
+                kase = 1;
+                jump = 3;
+                internalcomplexrcondsaveall(ref isave, ref rsave, ref i, ref iter, ref j, ref jlast, ref jump, ref absxi, ref altsgn, ref estold, ref temp);
+                return;
+            }
+            
+            //
+            // ENTRY   (JUMP = 3)
+            // X HAS BEEN OVERWRITTEN BY A*X.
+            //
+            if( jump==3 )
+            {
+                for(i_=1; i_<=n;i_++)
+                {
+                    v[i_] = x[i_];
+                }
+                estold = est;
+                est = internalcomplexrcondscsum1(v, n);
+                
+                //
+                // TEST FOR CYCLING.
+                //
+                if( (double)(est)<=(double)(estold) )
+                {
+                    
+                    //
+                    // ITERATION COMPLETE.  FINAL STAGE.
+                    //
+                    altsgn = 1;
+                    for(i=1; i<=n; i++)
+                    {
+                        x[i] = altsgn*(1+(double)(i-1)/(double)(n-1));
+                        altsgn = -altsgn;
+                    }
+                    kase = 1;
+                    jump = 5;
+                    internalcomplexrcondsaveall(ref isave, ref rsave, ref i, ref iter, ref j, ref jlast, ref jump, ref absxi, ref altsgn, ref estold, ref temp);
+                    return;
+                }
+                for(i=1; i<=n; i++)
+                {
+                    absxi = math.abscomplex(x[i]);
+                    if( (double)(absxi)>(double)(safmin) )
+                    {
+                        x[i] = x[i]/absxi;
+                    }
+                    else
+                    {
+                        x[i] = 1;
+                    }
+                }
+                kase = 2;
+                jump = 4;
+                internalcomplexrcondsaveall(ref isave, ref rsave, ref i, ref iter, ref j, ref jlast, ref jump, ref absxi, ref altsgn, ref estold, ref temp);
+                return;
+            }
+            
+            //
+            // ENTRY   (JUMP = 4)
+            // X HAS BEEN OVERWRITTEN BY CTRANS(A)*X.
+            //
+            if( jump==4 )
+            {
+                jlast = j;
+                j = internalcomplexrcondicmax1(x, n);
+                if( (double)(math.abscomplex(x[jlast]))!=(double)(math.abscomplex(x[j])) && iter<itmax )
+                {
+                    iter = iter+1;
+                    
+                    //
+                    // MAIN LOOP - ITERATIONS 2,3,...,ITMAX.
+                    //
+                    for(i=1; i<=n; i++)
+                    {
+                        x[i] = 0;
+                    }
+                    x[j] = 1;
+                    kase = 1;
+                    jump = 3;
+                    internalcomplexrcondsaveall(ref isave, ref rsave, ref i, ref iter, ref j, ref jlast, ref jump, ref absxi, ref altsgn, ref estold, ref temp);
+                    return;
+                }
+                
+                //
+                // ITERATION COMPLETE.  FINAL STAGE.
+                //
+                altsgn = 1;
+                for(i=1; i<=n; i++)
+                {
+                    x[i] = altsgn*(1+(double)(i-1)/(double)(n-1));
+                    altsgn = -altsgn;
+                }
+                kase = 1;
+                jump = 5;
+                internalcomplexrcondsaveall(ref isave, ref rsave, ref i, ref iter, ref j, ref jlast, ref jump, ref absxi, ref altsgn, ref estold, ref temp);
+                return;
+            }
+            
+            //
+            // ENTRY   (JUMP = 5)
+            // X HAS BEEN OVERWRITTEN BY A*X.
+            //
+            if( jump==5 )
+            {
+                temp = 2*(internalcomplexrcondscsum1(x, n)/(3*n));
+                if( (double)(temp)>(double)(est) )
+                {
+                    for(i_=1; i_<=n;i_++)
+                    {
+                        v[i_] = x[i_];
+                    }
+                    est = temp;
+                }
+                kase = 0;
+                internalcomplexrcondsaveall(ref isave, ref rsave, ref i, ref iter, ref j, ref jlast, ref jump, ref absxi, ref altsgn, ref estold, ref temp);
+                return;
+            }
+        }
+
+
+        private static double internalcomplexrcondscsum1(complex[] x,
+            int n)
+        {
+            double result = 0;
+            int i = 0;
+
+            result = 0;
+            for(i=1; i<=n; i++)
+            {
+                result = result+math.abscomplex(x[i]);
+            }
+            return result;
+        }
+
+
+        private static int internalcomplexrcondicmax1(complex[] x,
+            int n)
+        {
+            int result = 0;
+            int i = 0;
+            double m = 0;
+
+            result = 1;
+            m = math.abscomplex(x[1]);
+            for(i=2; i<=n; i++)
+            {
+                if( (double)(math.abscomplex(x[i]))>(double)(m) )
+                {
+                    result = i;
+                    m = math.abscomplex(x[i]);
+                }
+            }
+            return result;
+        }
+
+
+        private static void internalcomplexrcondsaveall(ref int[] isave,
+            ref double[] rsave,
+            ref int i,
+            ref int iter,
+            ref int j,
+            ref int jlast,
+            ref int jump,
+            ref double absxi,
+            ref double altsgn,
+            ref double estold,
+            ref double temp)
+        {
+            isave[0] = i;
+            isave[1] = iter;
+            isave[2] = j;
+            isave[3] = jlast;
+            isave[4] = jump;
+            rsave[0] = absxi;
+            rsave[1] = altsgn;
+            rsave[2] = estold;
+            rsave[3] = temp;
+        }
+
+
+        private static void internalcomplexrcondloadall(ref int[] isave,
+            ref double[] rsave,
+            ref int i,
+            ref int iter,
+            ref int j,
+            ref int jlast,
+            ref int jump,
+            ref double absxi,
+            ref double altsgn,
+            ref double estold,
+            ref double temp)
+        {
+            i = isave[0];
+            iter = isave[1];
+            j = isave[2];
+            jlast = isave[3];
+            jump = isave[4];
+            absxi = rsave[0];
+            altsgn = rsave[1];
+            estold = rsave[2];
+            temp = rsave[3];
+        }
+
+
+    }
+    public class matinv
+    {
+        /*************************************************************************
+        Matrix inverse report:
+        * R1    reciprocal of condition number in 1-norm
+        * RInf  reciprocal of condition number in inf-norm
+        *************************************************************************/
+        public class matinvreport
+        {
+            public double r1;
+            public double rinf;
+        };
+
+
+
+
+        /*************************************************************************
+        Inversion of a matrix given by its LU decomposition.
+
+        INPUT PARAMETERS:
+            A       -   LU decomposition of the matrix
+                        (output of RMatrixLU subroutine).
+            Pivots  -   table of permutations
+                        (the output of RMatrixLU subroutine).
+            N       -   size of matrix A (optional) :
+                        * if given, only principal NxN submatrix is processed  and
+                          overwritten. other elements are unchanged.
+                        * if not given,  size  is  automatically  determined  from
+                          matrix size (A must be square matrix)
+
+        OUTPUT PARAMETERS:
+            Info    -   return code:
+                        * -3    A is singular, or VERY close to singular.
+                                it is filled by zeros in such cases.
+                        *  1    task is solved (but matrix A may be ill-conditioned,
+                                check R1/RInf parameters for condition numbers).
+            Rep     -   solver report, see below for more info
+            A       -   inverse of matrix A.
+                        Array whose indexes range within [0..N-1, 0..N-1].
+
+        SOLVER REPORT
+
+        Subroutine sets following fields of the Rep structure:
+        * R1        reciprocal of condition number: 1/cond(A), 1-norm.
+        * RInf      reciprocal of condition number: 1/cond(A), inf-norm.
+
+          -- ALGLIB routine --
+             05.02.2010
+             Bochkanov Sergey
+        *************************************************************************/
+        public static void rmatrixluinverse(ref double[,] a,
+            int[] pivots,
+            int n,
+            ref int info,
+            matinvreport rep)
+        {
+            double[] work = new double[0];
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            double v = 0;
+
+            info = 0;
+
+            alglib.ap.assert(n>0, "RMatrixLUInverse: N<=0!");
+            alglib.ap.assert(alglib.ap.cols(a)>=n, "RMatrixLUInverse: cols(A)<N!");
+            alglib.ap.assert(alglib.ap.rows(a)>=n, "RMatrixLUInverse: rows(A)<N!");
+            alglib.ap.assert(alglib.ap.len(pivots)>=n, "RMatrixLUInverse: len(Pivots)<N!");
+            alglib.ap.assert(apserv.apservisfinitematrix(a, n, n), "RMatrixLUInverse: A contains infinite or NaN values!");
+            info = 1;
+            for(i=0; i<=n-1; i++)
+            {
+                if( pivots[i]>n-1 || pivots[i]<i )
+                {
+                    info = -1;
+                }
+            }
+            alglib.ap.assert(info>0, "RMatrixLUInverse: incorrect Pivots array!");
+            
+            //
+            // calculate condition numbers
+            //
+            rep.r1 = rcond.rmatrixlurcond1(a, n);
+            rep.rinf = rcond.rmatrixlurcondinf(a, n);
+            if( (double)(rep.r1)<(double)(rcond.rcondthreshold()) || (double)(rep.rinf)<(double)(rcond.rcondthreshold()) )
+            {
+                for(i=0; i<=n-1; i++)
+                {
+                    for(j=0; j<=n-1; j++)
+                    {
+                        a[i,j] = 0;
+                    }
+                }
+                rep.r1 = 0;
+                rep.rinf = 0;
+                info = -3;
+                return;
+            }
+            
+            //
+            // Call cache-oblivious code
+            //
+            work = new double[n];
+            rmatrixluinverserec(ref a, 0, n, ref work, ref info, rep);
+            
+            //
+            // apply permutations
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                for(j=n-2; j>=0; j--)
+                {
+                    k = pivots[j];
+                    v = a[i,j];
+                    a[i,j] = a[i,k];
+                    a[i,k] = v;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Inversion of a general matrix.
+
+        Input parameters:
+            A       -   matrix.
+            N       -   size of matrix A (optional) :
+                        * if given, only principal NxN submatrix is processed  and
+                          overwritten. other elements are unchanged.
+                        * if not given,  size  is  automatically  determined  from
+                          matrix size (A must be square matrix)
+
+        Output parameters:
+            Info    -   return code, same as in RMatrixLUInverse
+            Rep     -   solver report, same as in RMatrixLUInverse
+            A       -   inverse of matrix A, same as in RMatrixLUInverse
+
+        Result:
+            True, if the matrix is not singular.
+            False, if the matrix is singular.
+
+          -- ALGLIB --
+             Copyright 2005-2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rmatrixinverse(ref double[,] a,
+            int n,
+            ref int info,
+            matinvreport rep)
+        {
+            int[] pivots = new int[0];
+
+            info = 0;
+
+            alglib.ap.assert(n>0, "RMatrixInverse: N<=0!");
+            alglib.ap.assert(alglib.ap.cols(a)>=n, "RMatrixInverse: cols(A)<N!");
+            alglib.ap.assert(alglib.ap.rows(a)>=n, "RMatrixInverse: rows(A)<N!");
+            alglib.ap.assert(apserv.apservisfinitematrix(a, n, n), "RMatrixInverse: A contains infinite or NaN values!");
+            trfac.rmatrixlu(ref a, n, n, ref pivots);
+            rmatrixluinverse(ref a, pivots, n, ref info, rep);
+        }
+
+
+        /*************************************************************************
+        Inversion of a matrix given by its LU decomposition.
+
+        INPUT PARAMETERS:
+            A       -   LU decomposition of the matrix
+                        (output of CMatrixLU subroutine).
+            Pivots  -   table of permutations
+                        (the output of CMatrixLU subroutine).
+            N       -   size of matrix A (optional) :
+                        * if given, only principal NxN submatrix is processed  and
+                          overwritten. other elements are unchanged.
+                        * if not given,  size  is  automatically  determined  from
+                          matrix size (A must be square matrix)
+
+        OUTPUT PARAMETERS:
+            Info    -   return code, same as in RMatrixLUInverse
+            Rep     -   solver report, same as in RMatrixLUInverse
+            A       -   inverse of matrix A, same as in RMatrixLUInverse
+
+          -- ALGLIB routine --
+             05.02.2010
+             Bochkanov Sergey
+        *************************************************************************/
+        public static void cmatrixluinverse(ref complex[,] a,
+            int[] pivots,
+            int n,
+            ref int info,
+            matinvreport rep)
+        {
+            complex[] work = new complex[0];
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            complex v = 0;
+
+            info = 0;
+
+            alglib.ap.assert(n>0, "CMatrixLUInverse: N<=0!");
+            alglib.ap.assert(alglib.ap.cols(a)>=n, "CMatrixLUInverse: cols(A)<N!");
+            alglib.ap.assert(alglib.ap.rows(a)>=n, "CMatrixLUInverse: rows(A)<N!");
+            alglib.ap.assert(alglib.ap.len(pivots)>=n, "CMatrixLUInverse: len(Pivots)<N!");
+            alglib.ap.assert(apserv.apservisfinitecmatrix(a, n, n), "CMatrixLUInverse: A contains infinite or NaN values!");
+            info = 1;
+            for(i=0; i<=n-1; i++)
+            {
+                if( pivots[i]>n-1 || pivots[i]<i )
+                {
+                    info = -1;
+                }
+            }
+            alglib.ap.assert(info>0, "CMatrixLUInverse: incorrect Pivots array!");
+            
+            //
+            // calculate condition numbers
+            //
+            rep.r1 = rcond.cmatrixlurcond1(a, n);
+            rep.rinf = rcond.cmatrixlurcondinf(a, n);
+            if( (double)(rep.r1)<(double)(rcond.rcondthreshold()) || (double)(rep.rinf)<(double)(rcond.rcondthreshold()) )
+            {
+                for(i=0; i<=n-1; i++)
+                {
+                    for(j=0; j<=n-1; j++)
+                    {
+                        a[i,j] = 0;
+                    }
+                }
+                rep.r1 = 0;
+                rep.rinf = 0;
+                info = -3;
+                return;
+            }
+            
+            //
+            // Call cache-oblivious code
+            //
+            work = new complex[n];
+            cmatrixluinverserec(ref a, 0, n, ref work, ref info, rep);
+            
+            //
+            // apply permutations
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                for(j=n-2; j>=0; j--)
+                {
+                    k = pivots[j];
+                    v = a[i,j];
+                    a[i,j] = a[i,k];
+                    a[i,k] = v;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Inversion of a general matrix.
+
+        Input parameters:
+            A       -   matrix
+            N       -   size of matrix A (optional) :
+                        * if given, only principal NxN submatrix is processed  and
+                          overwritten. other elements are unchanged.
+                        * if not given,  size  is  automatically  determined  from
+                          matrix size (A must be square matrix)
+
+        Output parameters:
+            Info    -   return code, same as in RMatrixLUInverse
+            Rep     -   solver report, same as in RMatrixLUInverse
+            A       -   inverse of matrix A, same as in RMatrixLUInverse
+
+          -- ALGLIB --
+             Copyright 2005 by Bochkanov Sergey
+        *************************************************************************/
+        public static void cmatrixinverse(ref complex[,] a,
+            int n,
+            ref int info,
+            matinvreport rep)
+        {
+            int[] pivots = new int[0];
+
+            info = 0;
+
+            alglib.ap.assert(n>0, "CRMatrixInverse: N<=0!");
+            alglib.ap.assert(alglib.ap.cols(a)>=n, "CRMatrixInverse: cols(A)<N!");
+            alglib.ap.assert(alglib.ap.rows(a)>=n, "CRMatrixInverse: rows(A)<N!");
+            alglib.ap.assert(apserv.apservisfinitecmatrix(a, n, n), "CMatrixInverse: A contains infinite or NaN values!");
+            trfac.cmatrixlu(ref a, n, n, ref pivots);
+            cmatrixluinverse(ref a, pivots, n, ref info, rep);
+        }
+
+
+        /*************************************************************************
+        Inversion of a symmetric positive definite matrix which is given
+        by Cholesky decomposition.
+
+        Input parameters:
+            A       -   Cholesky decomposition of the matrix to be inverted:
+                        A=U*U or A = L*L'.
+                        Output of  SPDMatrixCholesky subroutine.
+            N       -   size of matrix A (optional) :
+                        * if given, only principal NxN submatrix is processed  and
+                          overwritten. other elements are unchanged.
+                        * if not given,  size  is  automatically  determined  from
+                          matrix size (A must be square matrix)
+            IsUpper -   storage type (optional):
+                        * if True, symmetric  matrix  A  is  given  by  its  upper
+                          triangle, and the lower triangle isnt  used/changed  by
+                          function
+                        * if False,  symmetric matrix  A  is  given  by  its lower
+                          triangle, and the  upper triangle isnt used/changed  by
+                          function
+                        * if not given, lower half is used.
+
+        Output parameters:
+            Info    -   return code, same as in RMatrixLUInverse
+            Rep     -   solver report, same as in RMatrixLUInverse
+            A       -   inverse of matrix A, same as in RMatrixLUInverse
+
+          -- ALGLIB routine --
+             10.02.2010
+             Bochkanov Sergey
+        *************************************************************************/
+        public static void spdmatrixcholeskyinverse(ref double[,] a,
+            int n,
+            bool isupper,
+            ref int info,
+            matinvreport rep)
+        {
+            int i = 0;
+            int j = 0;
+            double[] tmp = new double[0];
+            matinvreport rep2 = new matinvreport();
+            bool f = new bool();
+
+            info = 0;
+
+            alglib.ap.assert(n>0, "SPDMatrixCholeskyInverse: N<=0!");
+            alglib.ap.assert(alglib.ap.cols(a)>=n, "SPDMatrixCholeskyInverse: cols(A)<N!");
+            alglib.ap.assert(alglib.ap.rows(a)>=n, "SPDMatrixCholeskyInverse: rows(A)<N!");
+            info = 1;
+            f = true;
+            for(i=0; i<=n-1; i++)
+            {
+                f = f && math.isfinite(a[i,i]);
+            }
+            alglib.ap.assert(f, "SPDMatrixCholeskyInverse: A contains infinite or NaN values!");
+            
+            //
+            // calculate condition numbers
+            //
+            rep.r1 = rcond.spdmatrixcholeskyrcond(a, n, isupper);
+            rep.rinf = rep.r1;
+            if( (double)(rep.r1)<(double)(rcond.rcondthreshold()) || (double)(rep.rinf)<(double)(rcond.rcondthreshold()) )
+            {
+                if( isupper )
+                {
+                    for(i=0; i<=n-1; i++)
+                    {
+                        for(j=i; j<=n-1; j++)
+                        {
+                            a[i,j] = 0;
+                        }
+                    }
+                }
+                else
+                {
+                    for(i=0; i<=n-1; i++)
+                    {
+                        for(j=0; j<=i; j++)
+                        {
+                            a[i,j] = 0;
+                        }
+                    }
+                }
+                rep.r1 = 0;
+                rep.rinf = 0;
+                info = -3;
+                return;
+            }
+            
+            //
+            // Inverse
+            //
+            tmp = new double[n];
+            spdmatrixcholeskyinverserec(ref a, 0, n, isupper, ref tmp);
+        }
+
+
+        /*************************************************************************
+        Inversion of a symmetric positive definite matrix.
+
+        Given an upper or lower triangle of a symmetric positive definite matrix,
+        the algorithm generates matrix A^-1 and saves the upper or lower triangle
+        depending on the input.
+
+        Input parameters:
+            A       -   matrix to be inverted (upper or lower triangle).
+                        Array with elements [0..N-1,0..N-1].
+            N       -   size of matrix A (optional) :
+                        * if given, only principal NxN submatrix is processed  and
+                          overwritten. other elements are unchanged.
+                        * if not given,  size  is  automatically  determined  from
+                          matrix size (A must be square matrix)
+            IsUpper -   storage type (optional):
+                        * if True, symmetric  matrix  A  is  given  by  its  upper
+                          triangle, and the lower triangle isnt  used/changed  by
+                          function
+                        * if False,  symmetric matrix  A  is  given  by  its lower
+                          triangle, and the  upper triangle isnt used/changed  by
+                          function
+                        * if not given,  both lower and upper  triangles  must  be
+                          filled.
+
+        Output parameters:
+            Info    -   return code, same as in RMatrixLUInverse
+            Rep     -   solver report, same as in RMatrixLUInverse
+            A       -   inverse of matrix A, same as in RMatrixLUInverse
+
+          -- ALGLIB routine --
+             10.02.2010
+             Bochkanov Sergey
+        *************************************************************************/
+        public static void spdmatrixinverse(ref double[,] a,
+            int n,
+            bool isupper,
+            ref int info,
+            matinvreport rep)
+        {
+            info = 0;
+
+            alglib.ap.assert(n>0, "SPDMatrixInverse: N<=0!");
+            alglib.ap.assert(alglib.ap.cols(a)>=n, "SPDMatrixInverse: cols(A)<N!");
+            alglib.ap.assert(alglib.ap.rows(a)>=n, "SPDMatrixInverse: rows(A)<N!");
+            alglib.ap.assert(apserv.isfinitertrmatrix(a, n, isupper), "SPDMatrixInverse: A contains infinite or NaN values!");
+            info = 1;
+            if( trfac.spdmatrixcholesky(ref a, n, isupper) )
+            {
+                spdmatrixcholeskyinverse(ref a, n, isupper, ref info, rep);
+            }
+            else
+            {
+                info = -3;
+            }
+        }
+
+
+        /*************************************************************************
+        Inversion of a Hermitian positive definite matrix which is given
+        by Cholesky decomposition.
+
+        Input parameters:
+            A       -   Cholesky decomposition of the matrix to be inverted:
+                        A=U*U or A = L*L'.
+                        Output of  HPDMatrixCholesky subroutine.
+            N       -   size of matrix A (optional) :
+                        * if given, only principal NxN submatrix is processed  and
+                          overwritten. other elements are unchanged.
+                        * if not given,  size  is  automatically  determined  from
+                          matrix size (A must be square matrix)
+            IsUpper -   storage type (optional):
+                        * if True, symmetric  matrix  A  is  given  by  its  upper
+                          triangle, and the lower triangle isnt  used/changed  by
+                          function
+                        * if False,  symmetric matrix  A  is  given  by  its lower
+                          triangle, and the  upper triangle isnt used/changed  by
+                          function
+                        * if not given, lower half is used.
+
+        Output parameters:
+            Info    -   return code, same as in RMatrixLUInverse
+            Rep     -   solver report, same as in RMatrixLUInverse
+            A       -   inverse of matrix A, same as in RMatrixLUInverse
+
+          -- ALGLIB routine --
+             10.02.2010
+             Bochkanov Sergey
+        *************************************************************************/
+        public static void hpdmatrixcholeskyinverse(ref complex[,] a,
+            int n,
+            bool isupper,
+            ref int info,
+            matinvreport rep)
+        {
+            int i = 0;
+            int j = 0;
+            matinvreport rep2 = new matinvreport();
+            complex[] tmp = new complex[0];
+            bool f = new bool();
+
+            info = 0;
+
+            alglib.ap.assert(n>0, "HPDMatrixCholeskyInverse: N<=0!");
+            alglib.ap.assert(alglib.ap.cols(a)>=n, "HPDMatrixCholeskyInverse: cols(A)<N!");
+            alglib.ap.assert(alglib.ap.rows(a)>=n, "HPDMatrixCholeskyInverse: rows(A)<N!");
+            f = true;
+            for(i=0; i<=n-1; i++)
+            {
+                f = (f && math.isfinite(a[i,i].x)) && math.isfinite(a[i,i].y);
+            }
+            alglib.ap.assert(f, "HPDMatrixCholeskyInverse: A contains infinite or NaN values!");
+            info = 1;
+            
+            //
+            // calculate condition numbers
+            //
+            rep.r1 = rcond.hpdmatrixcholeskyrcond(a, n, isupper);
+            rep.rinf = rep.r1;
+            if( (double)(rep.r1)<(double)(rcond.rcondthreshold()) || (double)(rep.rinf)<(double)(rcond.rcondthreshold()) )
+            {
+                if( isupper )
+                {
+                    for(i=0; i<=n-1; i++)
+                    {
+                        for(j=i; j<=n-1; j++)
+                        {
+                            a[i,j] = 0;
+                        }
+                    }
+                }
+                else
+                {
+                    for(i=0; i<=n-1; i++)
+                    {
+                        for(j=0; j<=i; j++)
+                        {
+                            a[i,j] = 0;
+                        }
+                    }
+                }
+                rep.r1 = 0;
+                rep.rinf = 0;
+                info = -3;
+                return;
+            }
+            
+            //
+            // Inverse
+            //
+            tmp = new complex[n];
+            hpdmatrixcholeskyinverserec(ref a, 0, n, isupper, ref tmp);
+        }
+
+
+        /*************************************************************************
+        Inversion of a Hermitian positive definite matrix.
+
+        Given an upper or lower triangle of a Hermitian positive definite matrix,
+        the algorithm generates matrix A^-1 and saves the upper or lower triangle
+        depending on the input.
+
+        Input parameters:
+            A       -   matrix to be inverted (upper or lower triangle).
+                        Array with elements [0..N-1,0..N-1].
+            N       -   size of matrix A (optional) :
+                        * if given, only principal NxN submatrix is processed  and
+                          overwritten. other elements are unchanged.
+                        * if not given,  size  is  automatically  determined  from
+                          matrix size (A must be square matrix)
+            IsUpper -   storage type (optional):
+                        * if True, symmetric  matrix  A  is  given  by  its  upper
+                          triangle, and the lower triangle isnt  used/changed  by
+                          function
+                        * if False,  symmetric matrix  A  is  given  by  its lower
+                          triangle, and the  upper triangle isnt used/changed  by
+                          function
+                        * if not given,  both lower and upper  triangles  must  be
+                          filled.
+
+        Output parameters:
+            Info    -   return code, same as in RMatrixLUInverse
+            Rep     -   solver report, same as in RMatrixLUInverse
+            A       -   inverse of matrix A, same as in RMatrixLUInverse
+
+          -- ALGLIB routine --
+             10.02.2010
+             Bochkanov Sergey
+        *************************************************************************/
+        public static void hpdmatrixinverse(ref complex[,] a,
+            int n,
+            bool isupper,
+            ref int info,
+            matinvreport rep)
+        {
+            info = 0;
+
+            alglib.ap.assert(n>0, "HPDMatrixInverse: N<=0!");
+            alglib.ap.assert(alglib.ap.cols(a)>=n, "HPDMatrixInverse: cols(A)<N!");
+            alglib.ap.assert(alglib.ap.rows(a)>=n, "HPDMatrixInverse: rows(A)<N!");
+            alglib.ap.assert(apserv.apservisfinitectrmatrix(a, n, isupper), "HPDMatrixInverse: A contains infinite or NaN values!");
+            info = 1;
+            if( trfac.hpdmatrixcholesky(ref a, n, isupper) )
+            {
+                hpdmatrixcholeskyinverse(ref a, n, isupper, ref info, rep);
+            }
+            else
+            {
+                info = -3;
+            }
+        }
+
+
+        /*************************************************************************
+        Triangular matrix inverse (real)
+
+        The subroutine inverts the following types of matrices:
+            * upper triangular
+            * upper triangular with unit diagonal
+            * lower triangular
+            * lower triangular with unit diagonal
+
+        In case of an upper (lower) triangular matrix,  the  inverse  matrix  will
+        also be upper (lower) triangular, and after the end of the algorithm,  the
+        inverse matrix replaces the source matrix. The elements  below (above) the
+        main diagonal are not changed by the algorithm.
+
+        If  the matrix  has a unit diagonal, the inverse matrix also  has  a  unit
+        diagonal, and the diagonal elements are not passed to the algorithm.
+
+        Input parameters:
+            A       -   matrix, array[0..N-1, 0..N-1].
+            N       -   size of matrix A (optional) :
+                        * if given, only principal NxN submatrix is processed  and
+                          overwritten. other elements are unchanged.
+                        * if not given,  size  is  automatically  determined  from
+                          matrix size (A must be square matrix)
+            IsUpper -   True, if the matrix is upper triangular.
+            IsUnit  -   diagonal type (optional):
+                        * if True, matrix has unit diagonal (a[i,i] are NOT used)
+                        * if False, matrix diagonal is arbitrary
+                        * if not given, False is assumed
+
+        Output parameters:
+            Info    -   same as for RMatrixLUInverse
+            Rep     -   same as for RMatrixLUInverse
+            A       -   same as for RMatrixLUInverse.
+
+          -- ALGLIB --
+             Copyright 05.02.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rmatrixtrinverse(ref double[,] a,
+            int n,
+            bool isupper,
+            bool isunit,
+            ref int info,
+            matinvreport rep)
+        {
+            int i = 0;
+            int j = 0;
+            double[] tmp = new double[0];
+
+            info = 0;
+
+            alglib.ap.assert(n>0, "RMatrixTRInverse: N<=0!");
+            alglib.ap.assert(alglib.ap.cols(a)>=n, "RMatrixTRInverse: cols(A)<N!");
+            alglib.ap.assert(alglib.ap.rows(a)>=n, "RMatrixTRInverse: rows(A)<N!");
+            alglib.ap.assert(apserv.isfinitertrmatrix(a, n, isupper), "RMatrixTRInverse: A contains infinite or NaN values!");
+            info = 1;
+            
+            //
+            // calculate condition numbers
+            //
+            rep.r1 = rcond.rmatrixtrrcond1(a, n, isupper, isunit);
+            rep.rinf = rcond.rmatrixtrrcondinf(a, n, isupper, isunit);
+            if( (double)(rep.r1)<(double)(rcond.rcondthreshold()) || (double)(rep.rinf)<(double)(rcond.rcondthreshold()) )
+            {
+                for(i=0; i<=n-1; i++)
+                {
+                    for(j=0; j<=n-1; j++)
+                    {
+                        a[i,j] = 0;
+                    }
+                }
+                rep.r1 = 0;
+                rep.rinf = 0;
+                info = -3;
+                return;
+            }
+            
+            //
+            // Invert
+            //
+            tmp = new double[n];
+            rmatrixtrinverserec(ref a, 0, n, isupper, isunit, ref tmp, ref info, rep);
+        }
+
+
+        /*************************************************************************
+        Triangular matrix inverse (complex)
+
+        The subroutine inverts the following types of matrices:
+            * upper triangular
+            * upper triangular with unit diagonal
+            * lower triangular
+            * lower triangular with unit diagonal
+
+        In case of an upper (lower) triangular matrix,  the  inverse  matrix  will
+        also be upper (lower) triangular, and after the end of the algorithm,  the
+        inverse matrix replaces the source matrix. The elements  below (above) the
+        main diagonal are not changed by the algorithm.
+
+        If  the matrix  has a unit diagonal, the inverse matrix also  has  a  unit
+        diagonal, and the diagonal elements are not passed to the algorithm.
+
+        Input parameters:
+            A       -   matrix, array[0..N-1, 0..N-1].
+            N       -   size of matrix A (optional) :
+                        * if given, only principal NxN submatrix is processed  and
+                          overwritten. other elements are unchanged.
+                        * if not given,  size  is  automatically  determined  from
+                          matrix size (A must be square matrix)
+            IsUpper -   True, if the matrix is upper triangular.
+            IsUnit  -   diagonal type (optional):
+                        * if True, matrix has unit diagonal (a[i,i] are NOT used)
+                        * if False, matrix diagonal is arbitrary
+                        * if not given, False is assumed
+
+        Output parameters:
+            Info    -   same as for RMatrixLUInverse
+            Rep     -   same as for RMatrixLUInverse
+            A       -   same as for RMatrixLUInverse.
+
+          -- ALGLIB --
+             Copyright 05.02.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void cmatrixtrinverse(ref complex[,] a,
+            int n,
+            bool isupper,
+            bool isunit,
+            ref int info,
+            matinvreport rep)
+        {
+            int i = 0;
+            int j = 0;
+            complex[] tmp = new complex[0];
+
+            info = 0;
+
+            alglib.ap.assert(n>0, "CMatrixTRInverse: N<=0!");
+            alglib.ap.assert(alglib.ap.cols(a)>=n, "CMatrixTRInverse: cols(A)<N!");
+            alglib.ap.assert(alglib.ap.rows(a)>=n, "CMatrixTRInverse: rows(A)<N!");
+            alglib.ap.assert(apserv.apservisfinitectrmatrix(a, n, isupper), "CMatrixTRInverse: A contains infinite or NaN values!");
+            info = 1;
+            
+            //
+            // calculate condition numbers
+            //
+            rep.r1 = rcond.cmatrixtrrcond1(a, n, isupper, isunit);
+            rep.rinf = rcond.cmatrixtrrcondinf(a, n, isupper, isunit);
+            if( (double)(rep.r1)<(double)(rcond.rcondthreshold()) || (double)(rep.rinf)<(double)(rcond.rcondthreshold()) )
+            {
+                for(i=0; i<=n-1; i++)
+                {
+                    for(j=0; j<=n-1; j++)
+                    {
+                        a[i,j] = 0;
+                    }
+                }
+                rep.r1 = 0;
+                rep.rinf = 0;
+                info = -3;
+                return;
+            }
+            
+            //
+            // Invert
+            //
+            tmp = new complex[n];
+            cmatrixtrinverserec(ref a, 0, n, isupper, isunit, ref tmp, ref info, rep);
+        }
+
+
+        /*************************************************************************
+        Triangular matrix inversion, recursive subroutine
+
+          -- ALGLIB --
+             05.02.2010, Bochkanov Sergey.
+             Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd.,
+             Courant Institute, Argonne National Lab, and Rice University
+             February 29, 1992.
+        *************************************************************************/
+        private static void rmatrixtrinverserec(ref double[,] a,
+            int offs,
+            int n,
+            bool isupper,
+            bool isunit,
+            ref double[] tmp,
+            ref int info,
+            matinvreport rep)
+        {
+            int n1 = 0;
+            int n2 = 0;
+            int i = 0;
+            int j = 0;
+            double v = 0;
+            double ajj = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            if( n<1 )
+            {
+                info = -1;
+                return;
+            }
+            
+            //
+            // Base case
+            //
+            if( n<=ablas.ablasblocksize(a) )
+            {
+                if( isupper )
+                {
+                    
+                    //
+                    // Compute inverse of upper triangular matrix.
+                    //
+                    for(j=0; j<=n-1; j++)
+                    {
+                        if( !isunit )
+                        {
+                            if( (double)(a[offs+j,offs+j])==(double)(0) )
+                            {
+                                info = -3;
+                                return;
+                            }
+                            a[offs+j,offs+j] = 1/a[offs+j,offs+j];
+                            ajj = -a[offs+j,offs+j];
+                        }
+                        else
+                        {
+                            ajj = -1;
+                        }
+                        
+                        //
+                        // Compute elements 1:j-1 of j-th column.
+                        //
+                        if( j>0 )
+                        {
+                            i1_ = (offs+0) - (0);
+                            for(i_=0; i_<=j-1;i_++)
+                            {
+                                tmp[i_] = a[i_+i1_,offs+j];
+                            }
+                            for(i=0; i<=j-1; i++)
+                            {
+                                if( i<j-1 )
+                                {
+                                    i1_ = (i+1)-(offs+i+1);
+                                    v = 0.0;
+                                    for(i_=offs+i+1; i_<=offs+j-1;i_++)
+                                    {
+                                        v += a[offs+i,i_]*tmp[i_+i1_];
+                                    }
+                                }
+                                else
+                                {
+                                    v = 0;
+                                }
+                                if( !isunit )
+                                {
+                                    a[offs+i,offs+j] = v+a[offs+i,offs+i]*tmp[i];
+                                }
+                                else
+                                {
+                                    a[offs+i,offs+j] = v+tmp[i];
+                                }
+                            }
+                            for(i_=offs+0; i_<=offs+j-1;i_++)
+                            {
+                                a[i_,offs+j] = ajj*a[i_,offs+j];
+                            }
+                        }
+                    }
+                }
+                else
+                {
+                    
+                    //
+                    // Compute inverse of lower triangular matrix.
+                    //
+                    for(j=n-1; j>=0; j--)
+                    {
+                        if( !isunit )
+                        {
+                            if( (double)(a[offs+j,offs+j])==(double)(0) )
+                            {
+                                info = -3;
+                                return;
+                            }
+                            a[offs+j,offs+j] = 1/a[offs+j,offs+j];
+                            ajj = -a[offs+j,offs+j];
+                        }
+                        else
+                        {
+                            ajj = -1;
+                        }
+                        if( j<n-1 )
+                        {
+                            
+                            //
+                            // Compute elements j+1:n of j-th column.
+                            //
+                            i1_ = (offs+j+1) - (j+1);
+                            for(i_=j+1; i_<=n-1;i_++)
+                            {
+                                tmp[i_] = a[i_+i1_,offs+j];
+                            }
+                            for(i=j+1; i<=n-1; i++)
+                            {
+                                if( i>j+1 )
+                                {
+                                    i1_ = (j+1)-(offs+j+1);
+                                    v = 0.0;
+                                    for(i_=offs+j+1; i_<=offs+i-1;i_++)
+                                    {
+                                        v += a[offs+i,i_]*tmp[i_+i1_];
+                                    }
+                                }
+                                else
+                                {
+                                    v = 0;
+                                }
+                                if( !isunit )
+                                {
+                                    a[offs+i,offs+j] = v+a[offs+i,offs+i]*tmp[i];
+                                }
+                                else
+                                {
+                                    a[offs+i,offs+j] = v+tmp[i];
+                                }
+                            }
+                            for(i_=offs+j+1; i_<=offs+n-1;i_++)
+                            {
+                                a[i_,offs+j] = ajj*a[i_,offs+j];
+                            }
+                        }
+                    }
+                }
+                return;
+            }
+            
+            //
+            // Recursive case
+            //
+            ablas.ablassplitlength(a, n, ref n1, ref n2);
+            if( n2>0 )
+            {
+                if( isupper )
+                {
+                    for(i=0; i<=n1-1; i++)
+                    {
+                        for(i_=offs+n1; i_<=offs+n-1;i_++)
+                        {
+                            a[offs+i,i_] = -1*a[offs+i,i_];
+                        }
+                    }
+                    ablas.rmatrixlefttrsm(n1, n2, a, offs, offs, isupper, isunit, 0, ref a, offs, offs+n1);
+                    ablas.rmatrixrighttrsm(n1, n2, a, offs+n1, offs+n1, isupper, isunit, 0, ref a, offs, offs+n1);
+                }
+                else
+                {
+                    for(i=0; i<=n2-1; i++)
+                    {
+                        for(i_=offs; i_<=offs+n1-1;i_++)
+                        {
+                            a[offs+n1+i,i_] = -1*a[offs+n1+i,i_];
+                        }
+                    }
+                    ablas.rmatrixrighttrsm(n2, n1, a, offs, offs, isupper, isunit, 0, ref a, offs+n1, offs);
+                    ablas.rmatrixlefttrsm(n2, n1, a, offs+n1, offs+n1, isupper, isunit, 0, ref a, offs+n1, offs);
+                }
+                rmatrixtrinverserec(ref a, offs+n1, n2, isupper, isunit, ref tmp, ref info, rep);
+            }
+            rmatrixtrinverserec(ref a, offs, n1, isupper, isunit, ref tmp, ref info, rep);
+        }
+
+
+        /*************************************************************************
+        Triangular matrix inversion, recursive subroutine
+
+          -- ALGLIB --
+             05.02.2010, Bochkanov Sergey.
+             Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd.,
+             Courant Institute, Argonne National Lab, and Rice University
+             February 29, 1992.
+        *************************************************************************/
+        private static void cmatrixtrinverserec(ref complex[,] a,
+            int offs,
+            int n,
+            bool isupper,
+            bool isunit,
+            ref complex[] tmp,
+            ref int info,
+            matinvreport rep)
+        {
+            int n1 = 0;
+            int n2 = 0;
+            int i = 0;
+            int j = 0;
+            complex v = 0;
+            complex ajj = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            if( n<1 )
+            {
+                info = -1;
+                return;
+            }
+            
+            //
+            // Base case
+            //
+            if( n<=ablas.ablascomplexblocksize(a) )
+            {
+                if( isupper )
+                {
+                    
+                    //
+                    // Compute inverse of upper triangular matrix.
+                    //
+                    for(j=0; j<=n-1; j++)
+                    {
+                        if( !isunit )
+                        {
+                            if( a[offs+j,offs+j]==0 )
+                            {
+                                info = -3;
+                                return;
+                            }
+                            a[offs+j,offs+j] = 1/a[offs+j,offs+j];
+                            ajj = -a[offs+j,offs+j];
+                        }
+                        else
+                        {
+                            ajj = -1;
+                        }
+                        
+                        //
+                        // Compute elements 1:j-1 of j-th column.
+                        //
+                        if( j>0 )
+                        {
+                            i1_ = (offs+0) - (0);
+                            for(i_=0; i_<=j-1;i_++)
+                            {
+                                tmp[i_] = a[i_+i1_,offs+j];
+                            }
+                            for(i=0; i<=j-1; i++)
+                            {
+                                if( i<j-1 )
+                                {
+                                    i1_ = (i+1)-(offs+i+1);
+                                    v = 0.0;
+                                    for(i_=offs+i+1; i_<=offs+j-1;i_++)
+                                    {
+                                        v += a[offs+i,i_]*tmp[i_+i1_];
+                                    }
+                                }
+                                else
+                                {
+                                    v = 0;
+                                }
+                                if( !isunit )
+                                {
+                                    a[offs+i,offs+j] = v+a[offs+i,offs+i]*tmp[i];
+                                }
+                                else
+                                {
+                                    a[offs+i,offs+j] = v+tmp[i];
+                                }
+                            }
+                            for(i_=offs+0; i_<=offs+j-1;i_++)
+                            {
+                                a[i_,offs+j] = ajj*a[i_,offs+j];
+                            }
+                        }
+                    }
+                }
+                else
+                {
+                    
+                    //
+                    // Compute inverse of lower triangular matrix.
+                    //
+                    for(j=n-1; j>=0; j--)
+                    {
+                        if( !isunit )
+                        {
+                            if( a[offs+j,offs+j]==0 )
+                            {
+                                info = -3;
+                                return;
+                            }
+                            a[offs+j,offs+j] = 1/a[offs+j,offs+j];
+                            ajj = -a[offs+j,offs+j];
+                        }
+                        else
+                        {
+                            ajj = -1;
+                        }
+                        if( j<n-1 )
+                        {
+                            
+                            //
+                            // Compute elements j+1:n of j-th column.
+                            //
+                            i1_ = (offs+j+1) - (j+1);
+                            for(i_=j+1; i_<=n-1;i_++)
+                            {
+                                tmp[i_] = a[i_+i1_,offs+j];
+                            }
+                            for(i=j+1; i<=n-1; i++)
+                            {
+                                if( i>j+1 )
+                                {
+                                    i1_ = (j+1)-(offs+j+1);
+                                    v = 0.0;
+                                    for(i_=offs+j+1; i_<=offs+i-1;i_++)
+                                    {
+                                        v += a[offs+i,i_]*tmp[i_+i1_];
+                                    }
+                                }
+                                else
+                                {
+                                    v = 0;
+                                }
+                                if( !isunit )
+                                {
+                                    a[offs+i,offs+j] = v+a[offs+i,offs+i]*tmp[i];
+                                }
+                                else
+                                {
+                                    a[offs+i,offs+j] = v+tmp[i];
+                                }
+                            }
+                            for(i_=offs+j+1; i_<=offs+n-1;i_++)
+                            {
+                                a[i_,offs+j] = ajj*a[i_,offs+j];
+                            }
+                        }
+                    }
+                }
+                return;
+            }
+            
+            //
+            // Recursive case
+            //
+            ablas.ablascomplexsplitlength(a, n, ref n1, ref n2);
+            if( n2>0 )
+            {
+                if( isupper )
+                {
+                    for(i=0; i<=n1-1; i++)
+                    {
+                        for(i_=offs+n1; i_<=offs+n-1;i_++)
+                        {
+                            a[offs+i,i_] = -1*a[offs+i,i_];
+                        }
+                    }
+                    ablas.cmatrixlefttrsm(n1, n2, a, offs, offs, isupper, isunit, 0, ref a, offs, offs+n1);
+                    ablas.cmatrixrighttrsm(n1, n2, a, offs+n1, offs+n1, isupper, isunit, 0, ref a, offs, offs+n1);
+                }
+                else
+                {
+                    for(i=0; i<=n2-1; i++)
+                    {
+                        for(i_=offs; i_<=offs+n1-1;i_++)
+                        {
+                            a[offs+n1+i,i_] = -1*a[offs+n1+i,i_];
+                        }
+                    }
+                    ablas.cmatrixrighttrsm(n2, n1, a, offs, offs, isupper, isunit, 0, ref a, offs+n1, offs);
+                    ablas.cmatrixlefttrsm(n2, n1, a, offs+n1, offs+n1, isupper, isunit, 0, ref a, offs+n1, offs);
+                }
+                cmatrixtrinverserec(ref a, offs+n1, n2, isupper, isunit, ref tmp, ref info, rep);
+            }
+            cmatrixtrinverserec(ref a, offs, n1, isupper, isunit, ref tmp, ref info, rep);
+        }
+
+
+        private static void rmatrixluinverserec(ref double[,] a,
+            int offs,
+            int n,
+            ref double[] work,
+            ref int info,
+            matinvreport rep)
+        {
+            int i = 0;
+            int j = 0;
+            double v = 0;
+            int n1 = 0;
+            int n2 = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            if( n<1 )
+            {
+                info = -1;
+                return;
+            }
+            
+            //
+            // Base case
+            //
+            if( n<=ablas.ablasblocksize(a) )
+            {
+                
+                //
+                // Form inv(U)
+                //
+                rmatrixtrinverserec(ref a, offs, n, true, false, ref work, ref info, rep);
+                if( info<=0 )
+                {
+                    return;
+                }
+                
+                //
+                // Solve the equation inv(A)*L = inv(U) for inv(A).
+                //
+                for(j=n-1; j>=0; j--)
+                {
+                    
+                    //
+                    // Copy current column of L to WORK and replace with zeros.
+                    //
+                    for(i=j+1; i<=n-1; i++)
+                    {
+                        work[i] = a[offs+i,offs+j];
+                        a[offs+i,offs+j] = 0;
+                    }
+                    
+                    //
+                    // Compute current column of inv(A).
+                    //
+                    if( j<n-1 )
+                    {
+                        for(i=0; i<=n-1; i++)
+                        {
+                            i1_ = (j+1)-(offs+j+1);
+                            v = 0.0;
+                            for(i_=offs+j+1; i_<=offs+n-1;i_++)
+                            {
+                                v += a[offs+i,i_]*work[i_+i1_];
+                            }
+                            a[offs+i,offs+j] = a[offs+i,offs+j]-v;
+                        }
+                    }
+                }
+                return;
+            }
+            
+            //
+            // Recursive code:
+            //
+            //         ( L1      )   ( U1  U12 )
+            // A    =  (         ) * (         )
+            //         ( L12  L2 )   (     U2  )
+            //
+            //         ( W   X )
+            // A^-1 =  (       )
+            //         ( Y   Z )
+            //
+            ablas.ablassplitlength(a, n, ref n1, ref n2);
+            alglib.ap.assert(n2>0, "LUInverseRec: internal error!");
+            
+            //
+            // X := inv(U1)*U12*inv(U2)
+            //
+            ablas.rmatrixlefttrsm(n1, n2, a, offs, offs, true, false, 0, ref a, offs, offs+n1);
+            ablas.rmatrixrighttrsm(n1, n2, a, offs+n1, offs+n1, true, false, 0, ref a, offs, offs+n1);
+            
+            //
+            // Y := inv(L2)*L12*inv(L1)
+            //
+            ablas.rmatrixlefttrsm(n2, n1, a, offs+n1, offs+n1, false, true, 0, ref a, offs+n1, offs);
+            ablas.rmatrixrighttrsm(n2, n1, a, offs, offs, false, true, 0, ref a, offs+n1, offs);
+            
+            //
+            // W := inv(L1*U1)+X*Y
+            //
+            rmatrixluinverserec(ref a, offs, n1, ref work, ref info, rep);
+            if( info<=0 )
+            {
+                return;
+            }
+            ablas.rmatrixgemm(n1, n1, n2, 1.0, a, offs, offs+n1, 0, a, offs+n1, offs, 0, 1.0, ref a, offs, offs);
+            
+            //
+            // X := -X*inv(L2)
+            // Y := -inv(U2)*Y
+            //
+            ablas.rmatrixrighttrsm(n1, n2, a, offs+n1, offs+n1, false, true, 0, ref a, offs, offs+n1);
+            for(i=0; i<=n1-1; i++)
+            {
+                for(i_=offs+n1; i_<=offs+n-1;i_++)
+                {
+                    a[offs+i,i_] = -1*a[offs+i,i_];
+                }
+            }
+            ablas.rmatrixlefttrsm(n2, n1, a, offs+n1, offs+n1, true, false, 0, ref a, offs+n1, offs);
+            for(i=0; i<=n2-1; i++)
+            {
+                for(i_=offs; i_<=offs+n1-1;i_++)
+                {
+                    a[offs+n1+i,i_] = -1*a[offs+n1+i,i_];
+                }
+            }
+            
+            //
+            // Z := inv(L2*U2)
+            //
+            rmatrixluinverserec(ref a, offs+n1, n2, ref work, ref info, rep);
+        }
+
+
+        private static void cmatrixluinverserec(ref complex[,] a,
+            int offs,
+            int n,
+            ref complex[] work,
+            ref int info,
+            matinvreport rep)
+        {
+            int i = 0;
+            int j = 0;
+            complex v = 0;
+            int n1 = 0;
+            int n2 = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            if( n<1 )
+            {
+                info = -1;
+                return;
+            }
+            
+            //
+            // Base case
+            //
+            if( n<=ablas.ablascomplexblocksize(a) )
+            {
+                
+                //
+                // Form inv(U)
+                //
+                cmatrixtrinverserec(ref a, offs, n, true, false, ref work, ref info, rep);
+                if( info<=0 )
+                {
+                    return;
+                }
+                
+                //
+                // Solve the equation inv(A)*L = inv(U) for inv(A).
+                //
+                for(j=n-1; j>=0; j--)
+                {
+                    
+                    //
+                    // Copy current column of L to WORK and replace with zeros.
+                    //
+                    for(i=j+1; i<=n-1; i++)
+                    {
+                        work[i] = a[offs+i,offs+j];
+                        a[offs+i,offs+j] = 0;
+                    }
+                    
+                    //
+                    // Compute current column of inv(A).
+                    //
+                    if( j<n-1 )
+                    {
+                        for(i=0; i<=n-1; i++)
+                        {
+                            i1_ = (j+1)-(offs+j+1);
+                            v = 0.0;
+                            for(i_=offs+j+1; i_<=offs+n-1;i_++)
+                            {
+                                v += a[offs+i,i_]*work[i_+i1_];
+                            }
+                            a[offs+i,offs+j] = a[offs+i,offs+j]-v;
+                        }
+                    }
+                }
+                return;
+            }
+            
+            //
+            // Recursive code:
+            //
+            //         ( L1      )   ( U1  U12 )
+            // A    =  (         ) * (         )
+            //         ( L12  L2 )   (     U2  )
+            //
+            //         ( W   X )
+            // A^-1 =  (       )
+            //         ( Y   Z )
+            //
+            ablas.ablascomplexsplitlength(a, n, ref n1, ref n2);
+            alglib.ap.assert(n2>0, "LUInverseRec: internal error!");
+            
+            //
+            // X := inv(U1)*U12*inv(U2)
+            //
+            ablas.cmatrixlefttrsm(n1, n2, a, offs, offs, true, false, 0, ref a, offs, offs+n1);
+            ablas.cmatrixrighttrsm(n1, n2, a, offs+n1, offs+n1, true, false, 0, ref a, offs, offs+n1);
+            
+            //
+            // Y := inv(L2)*L12*inv(L1)
+            //
+            ablas.cmatrixlefttrsm(n2, n1, a, offs+n1, offs+n1, false, true, 0, ref a, offs+n1, offs);
+            ablas.cmatrixrighttrsm(n2, n1, a, offs, offs, false, true, 0, ref a, offs+n1, offs);
+            
+            //
+            // W := inv(L1*U1)+X*Y
+            //
+            cmatrixluinverserec(ref a, offs, n1, ref work, ref info, rep);
+            if( info<=0 )
+            {
+                return;
+            }
+            ablas.cmatrixgemm(n1, n1, n2, 1.0, a, offs, offs+n1, 0, a, offs+n1, offs, 0, 1.0, ref a, offs, offs);
+            
+            //
+            // X := -X*inv(L2)
+            // Y := -inv(U2)*Y
+            //
+            ablas.cmatrixrighttrsm(n1, n2, a, offs+n1, offs+n1, false, true, 0, ref a, offs, offs+n1);
+            for(i=0; i<=n1-1; i++)
+            {
+                for(i_=offs+n1; i_<=offs+n-1;i_++)
+                {
+                    a[offs+i,i_] = -1*a[offs+i,i_];
+                }
+            }
+            ablas.cmatrixlefttrsm(n2, n1, a, offs+n1, offs+n1, true, false, 0, ref a, offs+n1, offs);
+            for(i=0; i<=n2-1; i++)
+            {
+                for(i_=offs; i_<=offs+n1-1;i_++)
+                {
+                    a[offs+n1+i,i_] = -1*a[offs+n1+i,i_];
+                }
+            }
+            
+            //
+            // Z := inv(L2*U2)
+            //
+            cmatrixluinverserec(ref a, offs+n1, n2, ref work, ref info, rep);
+        }
+
+
+        /*************************************************************************
+        Recursive subroutine for SPD inversion.
+
+          -- ALGLIB routine --
+             10.02.2010
+             Bochkanov Sergey
+        *************************************************************************/
+        private static void spdmatrixcholeskyinverserec(ref double[,] a,
+            int offs,
+            int n,
+            bool isupper,
+            ref double[] tmp)
+        {
+            int i = 0;
+            int j = 0;
+            double v = 0;
+            int n1 = 0;
+            int n2 = 0;
+            int info2 = 0;
+            matinvreport rep2 = new matinvreport();
+            int i_ = 0;
+            int i1_ = 0;
+
+            if( n<1 )
+            {
+                return;
+            }
+            
+            //
+            // Base case
+            //
+            if( n<=ablas.ablasblocksize(a) )
+            {
+                rmatrixtrinverserec(ref a, offs, n, isupper, false, ref tmp, ref info2, rep2);
+                if( isupper )
+                {
+                    
+                    //
+                    // Compute the product U * U'.
+                    // NOTE: we never assume that diagonal of U is real
+                    //
+                    for(i=0; i<=n-1; i++)
+                    {
+                        if( i==0 )
+                        {
+                            
+                            //
+                            // 1x1 matrix
+                            //
+                            a[offs+i,offs+i] = math.sqr(a[offs+i,offs+i]);
+                        }
+                        else
+                        {
+                            
+                            //
+                            // (I+1)x(I+1) matrix,
+                            //
+                            // ( A11  A12 )   ( A11^H        )   ( A11*A11^H+A12*A12^H  A12*A22^H )
+                            // (          ) * (              ) = (                                )
+                            // (      A22 )   ( A12^H  A22^H )   ( A22*A12^H            A22*A22^H )
+                            //
+                            // A11 is IxI, A22 is 1x1.
+                            //
+                            i1_ = (offs) - (0);
+                            for(i_=0; i_<=i-1;i_++)
+                            {
+                                tmp[i_] = a[i_+i1_,offs+i];
+                            }
+                            for(j=0; j<=i-1; j++)
+                            {
+                                v = a[offs+j,offs+i];
+                                i1_ = (j) - (offs+j);
+                                for(i_=offs+j; i_<=offs+i-1;i_++)
+                                {
+                                    a[offs+j,i_] = a[offs+j,i_] + v*tmp[i_+i1_];
+                                }
+                            }
+                            v = a[offs+i,offs+i];
+                            for(i_=offs; i_<=offs+i-1;i_++)
+                            {
+                                a[i_,offs+i] = v*a[i_,offs+i];
+                            }
+                            a[offs+i,offs+i] = math.sqr(a[offs+i,offs+i]);
+                        }
+                    }
+                }
+                else
+                {
+                    
+                    //
+                    // Compute the product L' * L
+                    // NOTE: we never assume that diagonal of L is real
+                    //
+                    for(i=0; i<=n-1; i++)
+                    {
+                        if( i==0 )
+                        {
+                            
+                            //
+                            // 1x1 matrix
+                            //
+                            a[offs+i,offs+i] = math.sqr(a[offs+i,offs+i]);
+                        }
+                        else
+                        {
+                            
+                            //
+                            // (I+1)x(I+1) matrix,
+                            //
+                            // ( A11^H  A21^H )   ( A11      )   ( A11^H*A11+A21^H*A21  A21^H*A22 )
+                            // (              ) * (          ) = (                                )
+                            // (        A22^H )   ( A21  A22 )   ( A22^H*A21            A22^H*A22 )
+                            //
+                            // A11 is IxI, A22 is 1x1.
+                            //
+                            i1_ = (offs) - (0);
+                            for(i_=0; i_<=i-1;i_++)
+                            {
+                                tmp[i_] = a[offs+i,i_+i1_];
+                            }
+                            for(j=0; j<=i-1; j++)
+                            {
+                                v = a[offs+i,offs+j];
+                                i1_ = (0) - (offs);
+                                for(i_=offs; i_<=offs+j;i_++)
+                                {
+                                    a[offs+j,i_] = a[offs+j,i_] + v*tmp[i_+i1_];
+                                }
+                            }
+                            v = a[offs+i,offs+i];
+                            for(i_=offs; i_<=offs+i-1;i_++)
+                            {
+                                a[offs+i,i_] = v*a[offs+i,i_];
+                            }
+                            a[offs+i,offs+i] = math.sqr(a[offs+i,offs+i]);
+                        }
+                    }
+                }
+                return;
+            }
+            
+            //
+            // Recursive code: triangular factor inversion merged with
+            // UU' or L'L multiplication
+            //
+            ablas.ablassplitlength(a, n, ref n1, ref n2);
+            
+            //
+            // form off-diagonal block of trangular inverse
+            //
+            if( isupper )
+            {
+                for(i=0; i<=n1-1; i++)
+                {
+                    for(i_=offs+n1; i_<=offs+n-1;i_++)
+                    {
+                        a[offs+i,i_] = -1*a[offs+i,i_];
+                    }
+                }
+                ablas.rmatrixlefttrsm(n1, n2, a, offs, offs, isupper, false, 0, ref a, offs, offs+n1);
+                ablas.rmatrixrighttrsm(n1, n2, a, offs+n1, offs+n1, isupper, false, 0, ref a, offs, offs+n1);
+            }
+            else
+            {
+                for(i=0; i<=n2-1; i++)
+                {
+                    for(i_=offs; i_<=offs+n1-1;i_++)
+                    {
+                        a[offs+n1+i,i_] = -1*a[offs+n1+i,i_];
+                    }
+                }
+                ablas.rmatrixrighttrsm(n2, n1, a, offs, offs, isupper, false, 0, ref a, offs+n1, offs);
+                ablas.rmatrixlefttrsm(n2, n1, a, offs+n1, offs+n1, isupper, false, 0, ref a, offs+n1, offs);
+            }
+            
+            //
+            // invert first diagonal block
+            //
+            spdmatrixcholeskyinverserec(ref a, offs, n1, isupper, ref tmp);
+            
+            //
+            // update first diagonal block with off-diagonal block,
+            // update off-diagonal block
+            //
+            if( isupper )
+            {
+                ablas.rmatrixsyrk(n1, n2, 1.0, a, offs, offs+n1, 0, 1.0, ref a, offs, offs, isupper);
+                ablas.rmatrixrighttrsm(n1, n2, a, offs+n1, offs+n1, isupper, false, 1, ref a, offs, offs+n1);
+            }
+            else
+            {
+                ablas.rmatrixsyrk(n1, n2, 1.0, a, offs+n1, offs, 1, 1.0, ref a, offs, offs, isupper);
+                ablas.rmatrixlefttrsm(n2, n1, a, offs+n1, offs+n1, isupper, false, 1, ref a, offs+n1, offs);
+            }
+            
+            //
+            // invert second diagonal block
+            //
+            spdmatrixcholeskyinverserec(ref a, offs+n1, n2, isupper, ref tmp);
+        }
+
+
+        /*************************************************************************
+        Recursive subroutine for HPD inversion.
+
+          -- ALGLIB routine --
+             10.02.2010
+             Bochkanov Sergey
+        *************************************************************************/
+        private static void hpdmatrixcholeskyinverserec(ref complex[,] a,
+            int offs,
+            int n,
+            bool isupper,
+            ref complex[] tmp)
+        {
+            int i = 0;
+            int j = 0;
+            complex v = 0;
+            int n1 = 0;
+            int n2 = 0;
+            int info2 = 0;
+            matinvreport rep2 = new matinvreport();
+            int i_ = 0;
+            int i1_ = 0;
+
+            if( n<1 )
+            {
+                return;
+            }
+            
+            //
+            // Base case
+            //
+            if( n<=ablas.ablascomplexblocksize(a) )
+            {
+                cmatrixtrinverserec(ref a, offs, n, isupper, false, ref tmp, ref info2, rep2);
+                if( isupper )
+                {
+                    
+                    //
+                    // Compute the product U * U'.
+                    // NOTE: we never assume that diagonal of U is real
+                    //
+                    for(i=0; i<=n-1; i++)
+                    {
+                        if( i==0 )
+                        {
+                            
+                            //
+                            // 1x1 matrix
+                            //
+                            a[offs+i,offs+i] = math.sqr(a[offs+i,offs+i].x)+math.sqr(a[offs+i,offs+i].y);
+                        }
+                        else
+                        {
+                            
+                            //
+                            // (I+1)x(I+1) matrix,
+                            //
+                            // ( A11  A12 )   ( A11^H        )   ( A11*A11^H+A12*A12^H  A12*A22^H )
+                            // (          ) * (              ) = (                                )
+                            // (      A22 )   ( A12^H  A22^H )   ( A22*A12^H            A22*A22^H )
+                            //
+                            // A11 is IxI, A22 is 1x1.
+                            //
+                            i1_ = (offs) - (0);
+                            for(i_=0; i_<=i-1;i_++)
+                            {
+                                tmp[i_] = math.conj(a[i_+i1_,offs+i]);
+                            }
+                            for(j=0; j<=i-1; j++)
+                            {
+                                v = a[offs+j,offs+i];
+                                i1_ = (j) - (offs+j);
+                                for(i_=offs+j; i_<=offs+i-1;i_++)
+                                {
+                                    a[offs+j,i_] = a[offs+j,i_] + v*tmp[i_+i1_];
+                                }
+                            }
+                            v = math.conj(a[offs+i,offs+i]);
+                            for(i_=offs; i_<=offs+i-1;i_++)
+                            {
+                                a[i_,offs+i] = v*a[i_,offs+i];
+                            }
+                            a[offs+i,offs+i] = math.sqr(a[offs+i,offs+i].x)+math.sqr(a[offs+i,offs+i].y);
+                        }
+                    }
+                }
+                else
+                {
+                    
+                    //
+                    // Compute the product L' * L
+                    // NOTE: we never assume that diagonal of L is real
+                    //
+                    for(i=0; i<=n-1; i++)
+                    {
+                        if( i==0 )
+                        {
+                            
+                            //
+                            // 1x1 matrix
+                            //
+                            a[offs+i,offs+i] = math.sqr(a[offs+i,offs+i].x)+math.sqr(a[offs+i,offs+i].y);
+                        }
+                        else
+                        {
+                            
+                            //
+                            // (I+1)x(I+1) matrix,
+                            //
+                            // ( A11^H  A21^H )   ( A11      )   ( A11^H*A11+A21^H*A21  A21^H*A22 )
+                            // (              ) * (          ) = (                                )
+                            // (        A22^H )   ( A21  A22 )   ( A22^H*A21            A22^H*A22 )
+                            //
+                            // A11 is IxI, A22 is 1x1.
+                            //
+                            i1_ = (offs) - (0);
+                            for(i_=0; i_<=i-1;i_++)
+                            {
+                                tmp[i_] = a[offs+i,i_+i1_];
+                            }
+                            for(j=0; j<=i-1; j++)
+                            {
+                                v = math.conj(a[offs+i,offs+j]);
+                                i1_ = (0) - (offs);
+                                for(i_=offs; i_<=offs+j;i_++)
+                                {
+                                    a[offs+j,i_] = a[offs+j,i_] + v*tmp[i_+i1_];
+                                }
+                            }
+                            v = math.conj(a[offs+i,offs+i]);
+                            for(i_=offs; i_<=offs+i-1;i_++)
+                            {
+                                a[offs+i,i_] = v*a[offs+i,i_];
+                            }
+                            a[offs+i,offs+i] = math.sqr(a[offs+i,offs+i].x)+math.sqr(a[offs+i,offs+i].y);
+                        }
+                    }
+                }
+                return;
+            }
+            
+            //
+            // Recursive code: triangular factor inversion merged with
+            // UU' or L'L multiplication
+            //
+            ablas.ablascomplexsplitlength(a, n, ref n1, ref n2);
+            
+            //
+            // form off-diagonal block of trangular inverse
+            //
+            if( isupper )
+            {
+                for(i=0; i<=n1-1; i++)
+                {
+                    for(i_=offs+n1; i_<=offs+n-1;i_++)
+                    {
+                        a[offs+i,i_] = -1*a[offs+i,i_];
+                    }
+                }
+                ablas.cmatrixlefttrsm(n1, n2, a, offs, offs, isupper, false, 0, ref a, offs, offs+n1);
+                ablas.cmatrixrighttrsm(n1, n2, a, offs+n1, offs+n1, isupper, false, 0, ref a, offs, offs+n1);
+            }
+            else
+            {
+                for(i=0; i<=n2-1; i++)
+                {
+                    for(i_=offs; i_<=offs+n1-1;i_++)
+                    {
+                        a[offs+n1+i,i_] = -1*a[offs+n1+i,i_];
+                    }
+                }
+                ablas.cmatrixrighttrsm(n2, n1, a, offs, offs, isupper, false, 0, ref a, offs+n1, offs);
+                ablas.cmatrixlefttrsm(n2, n1, a, offs+n1, offs+n1, isupper, false, 0, ref a, offs+n1, offs);
+            }
+            
+            //
+            // invert first diagonal block
+            //
+            hpdmatrixcholeskyinverserec(ref a, offs, n1, isupper, ref tmp);
+            
+            //
+            // update first diagonal block with off-diagonal block,
+            // update off-diagonal block
+            //
+            if( isupper )
+            {
+                ablas.cmatrixsyrk(n1, n2, 1.0, a, offs, offs+n1, 0, 1.0, ref a, offs, offs, isupper);
+                ablas.cmatrixrighttrsm(n1, n2, a, offs+n1, offs+n1, isupper, false, 2, ref a, offs, offs+n1);
+            }
+            else
+            {
+                ablas.cmatrixsyrk(n1, n2, 1.0, a, offs+n1, offs, 2, 1.0, ref a, offs, offs, isupper);
+                ablas.cmatrixlefttrsm(n2, n1, a, offs+n1, offs+n1, isupper, false, 2, ref a, offs+n1, offs);
+            }
+            
+            //
+            // invert second diagonal block
+            //
+            hpdmatrixcholeskyinverserec(ref a, offs+n1, n2, isupper, ref tmp);
+        }
+
+
+    }
+    public class fbls
+    {
+        /*************************************************************************
+        Structure which stores state of linear CG solver between subsequent calls
+        of FBLSCgIteration(). Initialized with FBLSCGCreate().
+
+        USAGE:
+        1. call to FBLSCGCreate()
+        2. F:=FBLSCgIteration(State)
+        3. if F is False, iterations are over
+        4. otherwise, fill State.AX with A*x, State.XAX with x'*A*x
+        5. goto 2
+
+        If you want to rerminate iterations, pass zero or negative value to XAX.
+
+        FIELDS:
+            E1      -   2-norm of residual at the start
+            E2      -   2-norm of residual at the end
+            X       -   on return from FBLSCgIteration() it contains vector for
+                        matrix-vector product
+            AX      -   must be filled with A*x if FBLSCgIteration() returned True
+            XAX     -   must be filled with x'*A*x
+            XK      -   contains result (if FBLSCgIteration() returned False)
+            
+        Other fields are private and should not be used by outsiders.
+        *************************************************************************/
+        public class fblslincgstate
+        {
+            public double e1;
+            public double e2;
+            public double[] x;
+            public double[] ax;
+            public double xax;
+            public int n;
+            public double[] rk;
+            public double[] rk1;
+            public double[] xk;
+            public double[] xk1;
+            public double[] pk;
+            public double[] pk1;
+            public double[] b;
+            public rcommstate rstate;
+            public double[] tmp2;
+            public fblslincgstate()
+            {
+                x = new double[0];
+                ax = new double[0];
+                rk = new double[0];
+                rk1 = new double[0];
+                xk = new double[0];
+                xk1 = new double[0];
+                pk = new double[0];
+                pk1 = new double[0];
+                b = new double[0];
+                rstate = new rcommstate();
+                tmp2 = new double[0];
+            }
+        };
+
+
+
+
+        /*************************************************************************
+        Basic Cholesky solver for ScaleA*Cholesky(A)'*x = y.
+
+        This subroutine assumes that:
+        * A*ScaleA is well scaled
+        * A is well-conditioned, so no zero divisions or overflow may occur
+
+        INPUT PARAMETERS:
+            CHA     -   Cholesky decomposition of A
+            SqrtScaleA- square root of scale factor ScaleA
+            N       -   matrix size, N>=0.
+            IsUpper -   storage type
+            XB      -   right part
+            Tmp     -   buffer; function automatically allocates it, if it is  too
+                        small.  It  can  be  reused  if function is called several
+                        times.
+                        
+        OUTPUT PARAMETERS:
+            XB      -   solution
+
+        NOTE 1: no assertion or tests are done during algorithm operation
+        NOTE 2: N=0 will force algorithm to silently return
+
+          -- ALGLIB --
+             Copyright 13.10.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void fblscholeskysolve(double[,] cha,
+            double sqrtscalea,
+            int n,
+            bool isupper,
+            ref double[] xb,
+            ref double[] tmp)
+        {
+            int i = 0;
+            double v = 0;
+            int i_ = 0;
+
+            if( n==0 )
+            {
+                return;
+            }
+            if( alglib.ap.len(tmp)<n )
+            {
+                tmp = new double[n];
+            }
+            
+            //
+            // A = L*L' or A=U'*U
+            //
+            if( isupper )
+            {
+                
+                //
+                // Solve U'*y=b first.
+                //
+                for(i=0; i<=n-1; i++)
+                {
+                    xb[i] = xb[i]/(sqrtscalea*cha[i,i]);
+                    if( i<n-1 )
+                    {
+                        v = xb[i];
+                        for(i_=i+1; i_<=n-1;i_++)
+                        {
+                            tmp[i_] = sqrtscalea*cha[i,i_];
+                        }
+                        for(i_=i+1; i_<=n-1;i_++)
+                        {
+                            xb[i_] = xb[i_] - v*tmp[i_];
+                        }
+                    }
+                }
+                
+                //
+                // Solve U*x=y then.
+                //
+                for(i=n-1; i>=0; i--)
+                {
+                    if( i<n-1 )
+                    {
+                        for(i_=i+1; i_<=n-1;i_++)
+                        {
+                            tmp[i_] = sqrtscalea*cha[i,i_];
+                        }
+                        v = 0.0;
+                        for(i_=i+1; i_<=n-1;i_++)
+                        {
+                            v += tmp[i_]*xb[i_];
+                        }
+                        xb[i] = xb[i]-v;
+                    }
+                    xb[i] = xb[i]/(sqrtscalea*cha[i,i]);
+                }
+            }
+            else
+            {
+                
+                //
+                // Solve L*y=b first
+                //
+                for(i=0; i<=n-1; i++)
+                {
+                    if( i>0 )
+                    {
+                        for(i_=0; i_<=i-1;i_++)
+                        {
+                            tmp[i_] = sqrtscalea*cha[i,i_];
+                        }
+                        v = 0.0;
+                        for(i_=0; i_<=i-1;i_++)
+                        {
+                            v += tmp[i_]*xb[i_];
+                        }
+                        xb[i] = xb[i]-v;
+                    }
+                    xb[i] = xb[i]/(sqrtscalea*cha[i,i]);
+                }
+                
+                //
+                // Solve L'*x=y then.
+                //
+                for(i=n-1; i>=0; i--)
+                {
+                    xb[i] = xb[i]/(sqrtscalea*cha[i,i]);
+                    if( i>0 )
+                    {
+                        v = xb[i];
+                        for(i_=0; i_<=i-1;i_++)
+                        {
+                            tmp[i_] = sqrtscalea*cha[i,i_];
+                        }
+                        for(i_=0; i_<=i-1;i_++)
+                        {
+                            xb[i_] = xb[i_] - v*tmp[i_];
+                        }
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Fast basic linear solver: linear SPD CG
+
+        Solves (A^T*A + alpha*I)*x = b where:
+        * A is MxN matrix
+        * alpha>0 is a scalar
+        * I is NxN identity matrix
+        * b is Nx1 vector
+        * X is Nx1 unknown vector.
+
+        N iterations of linear conjugate gradient are used to solve problem.
+
+        INPUT PARAMETERS:
+            A   -   array[M,N], matrix
+            M   -   number of rows
+            N   -   number of unknowns
+            B   -   array[N], right part
+            X   -   initial approxumation, array[N]
+            Buf -   buffer; function automatically allocates it, if it is too
+                    small. It can be reused if function is called several times
+                    with same M and N.
+                    
+        OUTPUT PARAMETERS:
+            X   -   improved solution
+            
+        NOTES:
+        *   solver checks quality of improved solution. If (because of problem
+            condition number, numerical noise, etc.) new solution is WORSE than
+            original approximation, then original approximation is returned.
+        *   solver assumes that both A, B, Alpha are well scaled (i.e. they are
+            less than sqrt(overflow) and greater than sqrt(underflow)).
+            
+          -- ALGLIB --
+             Copyright 20.08.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void fblssolvecgx(double[,] a,
+            int m,
+            int n,
+            double alpha,
+            double[] b,
+            ref double[] x,
+            ref double[] buf)
+        {
+            int k = 0;
+            int offsrk = 0;
+            int offsrk1 = 0;
+            int offsxk = 0;
+            int offsxk1 = 0;
+            int offspk = 0;
+            int offspk1 = 0;
+            int offstmp1 = 0;
+            int offstmp2 = 0;
+            int bs = 0;
+            double e1 = 0;
+            double e2 = 0;
+            double rk2 = 0;
+            double rk12 = 0;
+            double pap = 0;
+            double s = 0;
+            double betak = 0;
+            double v1 = 0;
+            double v2 = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            
+            //
+            // Test for special case: B=0
+            //
+            v1 = 0.0;
+            for(i_=0; i_<=n-1;i_++)
+            {
+                v1 += b[i_]*b[i_];
+            }
+            if( (double)(v1)==(double)(0) )
+            {
+                for(k=0; k<=n-1; k++)
+                {
+                    x[k] = 0;
+                }
+                return;
+            }
+            
+            //
+            // Offsets inside Buf for:
+            // * R[K], R[K+1]
+            // * X[K], X[K+1]
+            // * P[K], P[K+1]
+            // * Tmp1 - array[M], Tmp2 - array[N]
+            //
+            offsrk = 0;
+            offsrk1 = offsrk+n;
+            offsxk = offsrk1+n;
+            offsxk1 = offsxk+n;
+            offspk = offsxk1+n;
+            offspk1 = offspk+n;
+            offstmp1 = offspk1+n;
+            offstmp2 = offstmp1+m;
+            bs = offstmp2+n;
+            if( alglib.ap.len(buf)<bs )
+            {
+                buf = new double[bs];
+            }
+            
+            //
+            // x(0) = x
+            //
+            i1_ = (0) - (offsxk);
+            for(i_=offsxk; i_<=offsxk+n-1;i_++)
+            {
+                buf[i_] = x[i_+i1_];
+            }
+            
+            //
+            // r(0) = b-A*x(0)
+            // RK2 = r(0)'*r(0)
+            //
+            ablas.rmatrixmv(m, n, a, 0, 0, 0, buf, offsxk, ref buf, offstmp1);
+            ablas.rmatrixmv(n, m, a, 0, 0, 1, buf, offstmp1, ref buf, offstmp2);
+            i1_ = (offsxk) - (offstmp2);
+            for(i_=offstmp2; i_<=offstmp2+n-1;i_++)
+            {
+                buf[i_] = buf[i_] + alpha*buf[i_+i1_];
+            }
+            i1_ = (0) - (offsrk);
+            for(i_=offsrk; i_<=offsrk+n-1;i_++)
+            {
+                buf[i_] = b[i_+i1_];
+            }
+            i1_ = (offstmp2) - (offsrk);
+            for(i_=offsrk; i_<=offsrk+n-1;i_++)
+            {
+                buf[i_] = buf[i_] - buf[i_+i1_];
+            }
+            rk2 = 0.0;
+            for(i_=offsrk; i_<=offsrk+n-1;i_++)
+            {
+                rk2 += buf[i_]*buf[i_];
+            }
+            i1_ = (offsrk) - (offspk);
+            for(i_=offspk; i_<=offspk+n-1;i_++)
+            {
+                buf[i_] = buf[i_+i1_];
+            }
+            e1 = Math.Sqrt(rk2);
+            
+            //
+            // Cycle
+            //
+            for(k=0; k<=n-1; k++)
+            {
+                
+                //
+                // Calculate A*p(k) - store in Buf[OffsTmp2:OffsTmp2+N-1]
+                // and p(k)'*A*p(k)  - store in PAP
+                //
+                // If PAP=0, break (iteration is over)
+                //
+                ablas.rmatrixmv(m, n, a, 0, 0, 0, buf, offspk, ref buf, offstmp1);
+                v1 = 0.0;
+                for(i_=offstmp1; i_<=offstmp1+m-1;i_++)
+                {
+                    v1 += buf[i_]*buf[i_];
+                }
+                v2 = 0.0;
+                for(i_=offspk; i_<=offspk+n-1;i_++)
+                {
+                    v2 += buf[i_]*buf[i_];
+                }
+                pap = v1+alpha*v2;
+                ablas.rmatrixmv(n, m, a, 0, 0, 1, buf, offstmp1, ref buf, offstmp2);
+                i1_ = (offspk) - (offstmp2);
+                for(i_=offstmp2; i_<=offstmp2+n-1;i_++)
+                {
+                    buf[i_] = buf[i_] + alpha*buf[i_+i1_];
+                }
+                if( (double)(pap)==(double)(0) )
+                {
+                    break;
+                }
+                
+                //
+                // S = (r(k)'*r(k))/(p(k)'*A*p(k))
+                //
+                s = rk2/pap;
+                
+                //
+                // x(k+1) = x(k) + S*p(k)
+                //
+                i1_ = (offsxk) - (offsxk1);
+                for(i_=offsxk1; i_<=offsxk1+n-1;i_++)
+                {
+                    buf[i_] = buf[i_+i1_];
+                }
+                i1_ = (offspk) - (offsxk1);
+                for(i_=offsxk1; i_<=offsxk1+n-1;i_++)
+                {
+                    buf[i_] = buf[i_] + s*buf[i_+i1_];
+                }
+                
+                //
+                // r(k+1) = r(k) - S*A*p(k)
+                // RK12 = r(k+1)'*r(k+1)
+                //
+                // Break if r(k+1) small enough (when compared to r(k))
+                //
+                i1_ = (offsrk) - (offsrk1);
+                for(i_=offsrk1; i_<=offsrk1+n-1;i_++)
+                {
+                    buf[i_] = buf[i_+i1_];
+                }
+                i1_ = (offstmp2) - (offsrk1);
+                for(i_=offsrk1; i_<=offsrk1+n-1;i_++)
+                {
+                    buf[i_] = buf[i_] - s*buf[i_+i1_];
+                }
+                rk12 = 0.0;
+                for(i_=offsrk1; i_<=offsrk1+n-1;i_++)
+                {
+                    rk12 += buf[i_]*buf[i_];
+                }
+                if( (double)(Math.Sqrt(rk12))<=(double)(100*math.machineepsilon*Math.Sqrt(rk2)) )
+                {
+                    
+                    //
+                    // X(k) = x(k+1) before exit -
+                    // - because we expect to find solution at x(k)
+                    //
+                    i1_ = (offsxk1) - (offsxk);
+                    for(i_=offsxk; i_<=offsxk+n-1;i_++)
+                    {
+                        buf[i_] = buf[i_+i1_];
+                    }
+                    break;
+                }
+                
+                //
+                // BetaK = RK12/RK2
+                // p(k+1) = r(k+1)+betak*p(k)
+                //
+                betak = rk12/rk2;
+                i1_ = (offsrk1) - (offspk1);
+                for(i_=offspk1; i_<=offspk1+n-1;i_++)
+                {
+                    buf[i_] = buf[i_+i1_];
+                }
+                i1_ = (offspk) - (offspk1);
+                for(i_=offspk1; i_<=offspk1+n-1;i_++)
+                {
+                    buf[i_] = buf[i_] + betak*buf[i_+i1_];
+                }
+                
+                //
+                // r(k) := r(k+1)
+                // x(k) := x(k+1)
+                // p(k) := p(k+1)
+                //
+                i1_ = (offsrk1) - (offsrk);
+                for(i_=offsrk; i_<=offsrk+n-1;i_++)
+                {
+                    buf[i_] = buf[i_+i1_];
+                }
+                i1_ = (offsxk1) - (offsxk);
+                for(i_=offsxk; i_<=offsxk+n-1;i_++)
+                {
+                    buf[i_] = buf[i_+i1_];
+                }
+                i1_ = (offspk1) - (offspk);
+                for(i_=offspk; i_<=offspk+n-1;i_++)
+                {
+                    buf[i_] = buf[i_+i1_];
+                }
+                rk2 = rk12;
+            }
+            
+            //
+            // Calculate E2
+            //
+            ablas.rmatrixmv(m, n, a, 0, 0, 0, buf, offsxk, ref buf, offstmp1);
+            ablas.rmatrixmv(n, m, a, 0, 0, 1, buf, offstmp1, ref buf, offstmp2);
+            i1_ = (offsxk) - (offstmp2);
+            for(i_=offstmp2; i_<=offstmp2+n-1;i_++)
+            {
+                buf[i_] = buf[i_] + alpha*buf[i_+i1_];
+            }
+            i1_ = (0) - (offsrk);
+            for(i_=offsrk; i_<=offsrk+n-1;i_++)
+            {
+                buf[i_] = b[i_+i1_];
+            }
+            i1_ = (offstmp2) - (offsrk);
+            for(i_=offsrk; i_<=offsrk+n-1;i_++)
+            {
+                buf[i_] = buf[i_] - buf[i_+i1_];
+            }
+            v1 = 0.0;
+            for(i_=offsrk; i_<=offsrk+n-1;i_++)
+            {
+                v1 += buf[i_]*buf[i_];
+            }
+            e2 = Math.Sqrt(v1);
+            
+            //
+            // Output result (if it was improved)
+            //
+            if( (double)(e2)<(double)(e1) )
+            {
+                i1_ = (offsxk) - (0);
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    x[i_] = buf[i_+i1_];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Construction of linear conjugate gradient solver.
+
+        State parameter passed using "var" semantics (i.e. previous state  is  NOT
+        erased). When it is already initialized, we can reause prevously allocated
+        memory.
+
+        INPUT PARAMETERS:
+            X       -   initial solution
+            B       -   right part
+            N       -   system size
+            State   -   structure; may be preallocated, if we want to reuse memory
+
+        OUTPUT PARAMETERS:
+            State   -   structure which is used by FBLSCGIteration() to store
+                        algorithm state between subsequent calls.
+
+        NOTE: no error checking is done; caller must check all parameters, prevent
+              overflows, and so on.
+
+          -- ALGLIB --
+             Copyright 22.10.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void fblscgcreate(double[] x,
+            double[] b,
+            int n,
+            fblslincgstate state)
+        {
+            int i_ = 0;
+
+            if( alglib.ap.len(state.b)<n )
+            {
+                state.b = new double[n];
+            }
+            if( alglib.ap.len(state.rk)<n )
+            {
+                state.rk = new double[n];
+            }
+            if( alglib.ap.len(state.rk1)<n )
+            {
+                state.rk1 = new double[n];
+            }
+            if( alglib.ap.len(state.xk)<n )
+            {
+                state.xk = new double[n];
+            }
+            if( alglib.ap.len(state.xk1)<n )
+            {
+                state.xk1 = new double[n];
+            }
+            if( alglib.ap.len(state.pk)<n )
+            {
+                state.pk = new double[n];
+            }
+            if( alglib.ap.len(state.pk1)<n )
+            {
+                state.pk1 = new double[n];
+            }
+            if( alglib.ap.len(state.tmp2)<n )
+            {
+                state.tmp2 = new double[n];
+            }
+            if( alglib.ap.len(state.x)<n )
+            {
+                state.x = new double[n];
+            }
+            if( alglib.ap.len(state.ax)<n )
+            {
+                state.ax = new double[n];
+            }
+            state.n = n;
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.xk[i_] = x[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.b[i_] = b[i_];
+            }
+            state.rstate.ia = new int[1+1];
+            state.rstate.ra = new double[6+1];
+            state.rstate.stage = -1;
+        }
+
+
+        /*************************************************************************
+        Linear CG solver, function relying on reverse communication to calculate
+        matrix-vector products.
+
+        See comments for FBLSLinCGState structure for more info.
+
+          -- ALGLIB --
+             Copyright 22.10.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static bool fblscgiteration(fblslincgstate state)
+        {
+            bool result = new bool();
+            int n = 0;
+            int k = 0;
+            double rk2 = 0;
+            double rk12 = 0;
+            double pap = 0;
+            double s = 0;
+            double betak = 0;
+            double v1 = 0;
+            double v2 = 0;
+            int i_ = 0;
+
+            
+            //
+            // Reverse communication preparations
+            // I know it looks ugly, but it works the same way
+            // anywhere from C++ to Python.
+            //
+            // This code initializes locals by:
+            // * random values determined during code
+            //   generation - on first subroutine call
+            // * values from previous call - on subsequent calls
+            //
+            if( state.rstate.stage>=0 )
+            {
+                n = state.rstate.ia[0];
+                k = state.rstate.ia[1];
+                rk2 = state.rstate.ra[0];
+                rk12 = state.rstate.ra[1];
+                pap = state.rstate.ra[2];
+                s = state.rstate.ra[3];
+                betak = state.rstate.ra[4];
+                v1 = state.rstate.ra[5];
+                v2 = state.rstate.ra[6];
+            }
+            else
+            {
+                n = -983;
+                k = -989;
+                rk2 = -834;
+                rk12 = 900;
+                pap = -287;
+                s = 364;
+                betak = 214;
+                v1 = -338;
+                v2 = -686;
+            }
+            if( state.rstate.stage==0 )
+            {
+                goto lbl_0;
+            }
+            if( state.rstate.stage==1 )
+            {
+                goto lbl_1;
+            }
+            if( state.rstate.stage==2 )
+            {
+                goto lbl_2;
+            }
+            
+            //
+            // Routine body
+            //
+            
+            //
+            // prepare locals
+            //
+            n = state.n;
+            
+            //
+            // Test for special case: B=0
+            //
+            v1 = 0.0;
+            for(i_=0; i_<=n-1;i_++)
+            {
+                v1 += state.b[i_]*state.b[i_];
+            }
+            if( (double)(v1)==(double)(0) )
+            {
+                for(k=0; k<=n-1; k++)
+                {
+                    state.xk[k] = 0;
+                }
+                result = false;
+                return result;
+            }
+            
+            //
+            // r(0) = b-A*x(0)
+            // RK2 = r(0)'*r(0)
+            //
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.xk[i_];
+            }
+            state.rstate.stage = 0;
+            goto lbl_rcomm;
+        lbl_0:
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.rk[i_] = state.b[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.rk[i_] = state.rk[i_] - state.ax[i_];
+            }
+            rk2 = 0.0;
+            for(i_=0; i_<=n-1;i_++)
+            {
+                rk2 += state.rk[i_]*state.rk[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.pk[i_] = state.rk[i_];
+            }
+            state.e1 = Math.Sqrt(rk2);
+            
+            //
+            // Cycle
+            //
+            k = 0;
+        lbl_3:
+            if( k>n-1 )
+            {
+                goto lbl_5;
+            }
+            
+            //
+            // Calculate A*p(k) - store in State.Tmp2
+            // and p(k)'*A*p(k)  - store in PAP
+            //
+            // If PAP=0, break (iteration is over)
+            //
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.pk[i_];
+            }
+            state.rstate.stage = 1;
+            goto lbl_rcomm;
+        lbl_1:
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.tmp2[i_] = state.ax[i_];
+            }
+            pap = state.xax;
+            if( !math.isfinite(pap) )
+            {
+                goto lbl_5;
+            }
+            if( (double)(pap)<=(double)(0) )
+            {
+                goto lbl_5;
+            }
+            
+            //
+            // S = (r(k)'*r(k))/(p(k)'*A*p(k))
+            //
+            s = rk2/pap;
+            
+            //
+            // x(k+1) = x(k) + S*p(k)
+            //
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.xk1[i_] = state.xk[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.xk1[i_] = state.xk1[i_] + s*state.pk[i_];
+            }
+            
+            //
+            // r(k+1) = r(k) - S*A*p(k)
+            // RK12 = r(k+1)'*r(k+1)
+            //
+            // Break if r(k+1) small enough (when compared to r(k))
+            //
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.rk1[i_] = state.rk[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.rk1[i_] = state.rk1[i_] - s*state.tmp2[i_];
+            }
+            rk12 = 0.0;
+            for(i_=0; i_<=n-1;i_++)
+            {
+                rk12 += state.rk1[i_]*state.rk1[i_];
+            }
+            if( (double)(Math.Sqrt(rk12))<=(double)(100*math.machineepsilon*state.e1) )
+            {
+                
+                //
+                // X(k) = x(k+1) before exit -
+                // - because we expect to find solution at x(k)
+                //
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    state.xk[i_] = state.xk1[i_];
+                }
+                goto lbl_5;
+            }
+            
+            //
+            // BetaK = RK12/RK2
+            // p(k+1) = r(k+1)+betak*p(k)
+            //
+            // NOTE: we expect that BetaK won't overflow because of
+            // "Sqrt(RK12)<=100*MachineEpsilon*E1" test above.
+            //
+            betak = rk12/rk2;
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.pk1[i_] = state.rk1[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.pk1[i_] = state.pk1[i_] + betak*state.pk[i_];
+            }
+            
+            //
+            // r(k) := r(k+1)
+            // x(k) := x(k+1)
+            // p(k) := p(k+1)
+            //
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.rk[i_] = state.rk1[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.xk[i_] = state.xk1[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.pk[i_] = state.pk1[i_];
+            }
+            rk2 = rk12;
+            k = k+1;
+            goto lbl_3;
+        lbl_5:
+            
+            //
+            // Calculate E2
+            //
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.xk[i_];
+            }
+            state.rstate.stage = 2;
+            goto lbl_rcomm;
+        lbl_2:
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.rk[i_] = state.b[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.rk[i_] = state.rk[i_] - state.ax[i_];
+            }
+            v1 = 0.0;
+            for(i_=0; i_<=n-1;i_++)
+            {
+                v1 += state.rk[i_]*state.rk[i_];
+            }
+            state.e2 = Math.Sqrt(v1);
+            result = false;
+            return result;
+            
+            //
+            // Saving state
+            //
+        lbl_rcomm:
+            result = true;
+            state.rstate.ia[0] = n;
+            state.rstate.ia[1] = k;
+            state.rstate.ra[0] = rk2;
+            state.rstate.ra[1] = rk12;
+            state.rstate.ra[2] = pap;
+            state.rstate.ra[3] = s;
+            state.rstate.ra[4] = betak;
+            state.rstate.ra[5] = v1;
+            state.rstate.ra[6] = v2;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Fast  least  squares  solver,  solves  well  conditioned  system   without
+        performing  any  checks  for  degeneracy,  and using user-provided buffers
+        (which are automatically reallocated if too small).
+
+        This  function  is  intended  for solution of moderately sized systems. It
+        uses factorization algorithms based on Level 2 BLAS  operations,  thus  it
+        won't work efficiently on large scale systems.
+
+        INPUT PARAMETERS:
+            A       -   array[M,N], system matrix.
+                        Contents of A is destroyed during solution.
+            B       -   array[M], right part
+            M       -   number of equations
+            N       -   number of variables, N<=M
+            Tmp0, Tmp1, Tmp2-
+                        buffers; function automatically allocates them, if they are
+                        too  small. They can  be  reused  if  function  is   called 
+                        several times.
+                        
+        OUTPUT PARAMETERS:
+            B       -   solution (first N components, next M-N are zero)
+
+          -- ALGLIB --
+             Copyright 20.01.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void fblssolvels(ref double[,] a,
+            ref double[] b,
+            int m,
+            int n,
+            ref double[] tmp0,
+            ref double[] tmp1,
+            ref double[] tmp2)
+        {
+            int i = 0;
+            int k = 0;
+            double v = 0;
+            int i_ = 0;
+
+            alglib.ap.assert(n>0, "FBLSSolveLS: N<=0");
+            alglib.ap.assert(m>=n, "FBLSSolveLS: M<N");
+            alglib.ap.assert(alglib.ap.rows(a)>=m, "FBLSSolveLS: Rows(A)<M");
+            alglib.ap.assert(alglib.ap.cols(a)>=n, "FBLSSolveLS: Cols(A)<N");
+            alglib.ap.assert(alglib.ap.len(b)>=m, "FBLSSolveLS: Length(B)<M");
+            
+            //
+            // Allocate temporaries
+            //
+            apserv.rvectorsetlengthatleast(ref tmp0, Math.Max(m, n)+1);
+            apserv.rvectorsetlengthatleast(ref tmp1, Math.Max(m, n)+1);
+            apserv.rvectorsetlengthatleast(ref tmp2, Math.Min(m, n));
+            
+            //
+            // Call basecase QR
+            //
+            ortfac.rmatrixqrbasecase(ref a, m, n, ref tmp0, ref tmp1, ref tmp2);
+            
+            //
+            // Multiply B by Q'
+            //
+            for(k=0; k<=n-1; k++)
+            {
+                for(i=0; i<=k-1; i++)
+                {
+                    tmp0[i] = 0;
+                }
+                for(i_=k; i_<=m-1;i_++)
+                {
+                    tmp0[i_] = a[i_,k];
+                }
+                tmp0[k] = 1;
+                v = 0.0;
+                for(i_=k; i_<=m-1;i_++)
+                {
+                    v += tmp0[i_]*b[i_];
+                }
+                v = v*tmp2[k];
+                for(i_=k; i_<=m-1;i_++)
+                {
+                    b[i_] = b[i_] - v*tmp0[i_];
+                }
+            }
+            
+            //
+            // Solve triangular system
+            //
+            b[n-1] = b[n-1]/a[n-1,n-1];
+            for(i=n-2; i>=0; i--)
+            {
+                v = 0.0;
+                for(i_=i+1; i_<=n-1;i_++)
+                {
+                    v += a[i,i_]*b[i_];
+                }
+                b[i] = (b[i]-v)/a[i,i];
+            }
+            for(i=n; i<=m-1; i++)
+            {
+                b[i] = 0.0;
+            }
+        }
+
+
+    }
+    public class sparse
+    {
+        /*************************************************************************
+        Sparse matrix
+
+        You should use ALGLIB functions to work with sparse matrix.
+        Never try to access its fields directly!
+        *************************************************************************/
+        public class sparsematrix
+        {
+            public double[] vals;
+            public int[] idx;
+            public int[] ridx;
+            public int[] didx;
+            public int[] uidx;
+            public int matrixtype;
+            public int m;
+            public int n;
+            public int nfree;
+            public int ninitialized;
+            public sparsematrix()
+            {
+                vals = new double[0];
+                idx = new int[0];
+                ridx = new int[0];
+                didx = new int[0];
+                uidx = new int[0];
+            }
+        };
+
+
+
+
+        public const double desiredloadfactor = 0.66;
+        public const double maxloadfactor = 0.75;
+        public const double growfactor = 2.00;
+        public const int additional = 10;
+        public const int linalgswitch = 16;
+
+
+        /*************************************************************************
+        This function creates sparse matrix in a Hash-Table format.
+
+        This function creates Hast-Table matrix, which can be  converted  to  CRS
+        format after its initialization is over. Typical  usage  scenario  for  a
+        sparse matrix is:
+        1. creation in a Hash-Table format
+        2. insertion of the matrix elements
+        3. conversion to the CRS representation
+        4. matrix is passed to some linear algebra algorithm
+
+        Some  information  about  different matrix formats can be found below, in
+        the "NOTES" section.
+
+        INPUT PARAMETERS
+            M           -   number of rows in a matrix, M>=1
+            N           -   number of columns in a matrix, N>=1
+            K           -   K>=0, expected number of non-zero elements in a matrix.
+                            K can be inexact approximation, can be less than actual
+                            number  of  elements  (table will grow when needed) or 
+                            even zero).
+                            It is important to understand that although hash-table
+                            may grow automatically, it is better to  provide  good
+                            estimate of data size.
+
+        OUTPUT PARAMETERS
+            S           -   sparse M*N matrix in Hash-Table representation.
+                            All elements of the matrix are zero.
+
+        NOTE 1.
+
+        Sparse matrices can be stored using either Hash-Table  representation  or
+        Compressed  Row  Storage  representation. Hast-table is better suited for
+        querying   and   dynamic   operations   (thus,  it  is  used  for  matrix
+        initialization), but it is inefficient when you want to make some  linear 
+        algebra operations.
+
+        From the other side, CRS is better suited for linear algebra  operations,
+        but initialization is less convenient - you have to tell row sizes at the
+        initialization,  and  you  can  fill matrix only row by row, from left to 
+        right. CRS is also very inefficient when you want to find matrix  element 
+        by its index.
+
+        Thus,  Hash-Table  representation   does   not   support  linear  algebra
+        operations, while CRS format does not support modification of the  table.
+        Tables below outline information about these two formats:
+
+            OPERATIONS WITH MATRIX      HASH        CRS
+            create                      +           +
+            read element                +           +
+            modify element              +           
+            add value to element        +
+            A*x  (dense vector)                     +
+            A'*x (dense vector)                     +
+            A*X  (dense matrix)                     +
+            A'*X (dense matrix)                     +
+
+        NOTE 2.
+
+        Hash-tables use memory inefficiently, and they have to keep  some  amount
+        of the "spare memory" in order to have good performance. Hash  table  for
+        matrix with K non-zero elements will  need  C*K*(8+2*sizeof(int))  bytes,
+        where C is a small constant, about 1.5-2 in magnitude.
+
+        CRS storage, from the other side, is  more  memory-efficient,  and  needs
+        just K*(8+sizeof(int))+M*sizeof(int) bytes, where M is a number  of  rows
+        in a matrix.
+
+        When you convert from the Hash-Table to CRS  representation, all unneeded
+        memory will be freed.
+
+          -- ALGLIB PROJECT --
+             Copyright 14.10.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sparsecreate(int m,
+            int n,
+            int k,
+            sparsematrix s)
+        {
+            int i = 0;
+            int sz = 0;
+
+            alglib.ap.assert(m>0, "SparseCreate: M<=0");
+            alglib.ap.assert(n>0, "SparseCreate: N<=0");
+            alglib.ap.assert(k>=0, "SparseCreate: K<0");
+            sz = (int)Math.Round(k/desiredloadfactor+additional);
+            s.matrixtype = 0;
+            s.m = m;
+            s.n = n;
+            s.nfree = sz;
+            s.vals = new double[sz];
+            s.idx = new int[2*sz];
+            for(i=0; i<=sz-1; i++)
+            {
+                s.idx[2*i] = -1;
+            }
+        }
+
+
+        /*************************************************************************
+        This function creates sparse matrix in a CRS format (expert function for
+        situations when you are running out of memory).
+
+        This function creates CRS matrix. Typical usage scenario for a CRS matrix 
+        is:
+        1. creation (you have to tell number of non-zero elements at each row  at 
+           this moment)
+        2. insertion of the matrix elements (row by row, from left to right) 
+        3. matrix is passed to some linear algebra algorithm
+
+        This function is a memory-efficient alternative to SparseCreate(), but it
+        is more complex because it requires you to know in advance how large your
+        matrix is. Some  information about  different matrix formats can be found 
+        below, in the "NOTES" section.
+
+        INPUT PARAMETERS
+            M           -   number of rows in a matrix, M>=1
+            N           -   number of columns in a matrix, N>=1
+            NER         -   number of elements at each row, array[M], NER[i]>=0
+
+        OUTPUT PARAMETERS
+            S           -   sparse M*N matrix in CRS representation.
+                            You have to fill ALL non-zero elements by calling
+                            SparseSet() BEFORE you try to use this matrix.
+
+        NOTE 1.
+
+        Sparse matrices can be stored using either Hash-Table  representation  or
+        Compressed  Row  Storage  representation. Hast-table is better suited for
+        querying   and   dynamic   operations   (thus,  it  is  used  for  matrix
+        initialization), but it is inefficient when you want to make some  linear 
+        algebra operations.
+
+        From the other side, CRS is better suited for linear algebra  operations,
+        but initialization is less convenient - you have to tell row sizes at the
+        initialization,  and  you  can  fill matrix only row by row, from left to 
+        right. CRS is also very inefficient when you want to find matrix  element 
+        by its index.
+
+        Thus,  Hash-Table  representation   does   not   support  linear  algebra
+        operations, while CRS format does not support modification of the  table.
+        Tables below outline information about these two formats:
+
+            OPERATIONS WITH MATRIX      HASH        CRS
+            create                      +           +
+            read element                +           +
+            modify element              +           
+            add value to element        +
+            A*x  (dense vector)                     +
+            A'*x (dense vector)                     +
+            A*X  (dense matrix)                     +
+            A'*X (dense matrix)                     +
+
+        NOTE 2.
+
+        Hash-tables use memory inefficiently, and they have to keep  some  amount
+        of the "spare memory" in order to have good performance. Hash  table  for
+        matrix with K non-zero elements will  need  C*K*(8+2*sizeof(int))  bytes,
+        where C is a small constant, about 1.5-2 in magnitude.
+
+        CRS storage, from the other side, is  more  memory-efficient,  and  needs
+        just K*(8+sizeof(int))+M*sizeof(int) bytes, where M is a number  of  rows
+        in a matrix.
+
+        When you convert from the Hash-Table to CRS  representation, all unneeded
+        memory will be freed.
+
+          -- ALGLIB PROJECT --
+             Copyright 14.10.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sparsecreatecrs(int m,
+            int n,
+            int[] ner,
+            sparsematrix s)
+        {
+            int i = 0;
+            int noe = 0;
+
+            alglib.ap.assert(m>0, "SparseCreateCRS: M<=0");
+            alglib.ap.assert(n>0, "SparseCreateCRS: N<=0");
+            alglib.ap.assert(alglib.ap.len(ner)>=m, "SparseCreateCRS: Length(NER)<M");
+            noe = 0;
+            s.matrixtype = 1;
+            s.ninitialized = 0;
+            s.m = m;
+            s.n = n;
+            s.ridx = new int[s.m+1];
+            s.ridx[0] = 0;
+            for(i=0; i<=s.m-1; i++)
+            {
+                alglib.ap.assert(ner[i]>=0, "SparseCreateCRS: NER[] contains negative elements");
+                noe = noe+ner[i];
+                s.ridx[i+1] = s.ridx[i]+ner[i];
+            }
+            s.vals = new double[noe];
+            s.idx = new int[noe];
+            if( noe==0 )
+            {
+                sparseinitduidx(s);
+            }
+        }
+
+
+        /*************************************************************************
+        This function copies S0 to S1.
+
+        NOTE:  this  function  does  not verify its arguments, it just copies all
+        fields of the structure.
+
+          -- ALGLIB PROJECT --
+             Copyright 14.10.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sparsecopy(sparsematrix s0,
+            sparsematrix s1)
+        {
+            int l = 0;
+            int i = 0;
+
+            s1.matrixtype = s0.matrixtype;
+            s1.m = s0.m;
+            s1.n = s0.n;
+            s1.nfree = s0.nfree;
+            s1.ninitialized = s0.ninitialized;
+            
+            //
+            //initialization for arrays
+            //
+            l = alglib.ap.len(s0.vals);
+            s1.vals = new double[l];
+            for(i=0; i<=l-1; i++)
+            {
+                s1.vals[i] = s0.vals[i];
+            }
+            l = alglib.ap.len(s0.ridx);
+            s1.ridx = new int[l];
+            for(i=0; i<=l-1; i++)
+            {
+                s1.ridx[i] = s0.ridx[i];
+            }
+            l = alglib.ap.len(s0.idx);
+            s1.idx = new int[l];
+            for(i=0; i<=l-1; i++)
+            {
+                s1.idx[i] = s0.idx[i];
+            }
+            
+            //
+            //initalization for CRS-parameters
+            //
+            l = alglib.ap.len(s0.uidx);
+            s1.uidx = new int[l];
+            for(i=0; i<=l-1; i++)
+            {
+                s1.uidx[i] = s0.uidx[i];
+            }
+            l = alglib.ap.len(s0.didx);
+            s1.didx = new int[l];
+            for(i=0; i<=l-1; i++)
+            {
+                s1.didx[i] = s0.didx[i];
+            }
+        }
+
+
+        /*************************************************************************
+        This function adds value to S[i,j] - element of the sparse matrix. Matrix
+        must be in a Hash-Table mode.
+
+        In case S[i,j] already exists in the table, V i added to  its  value.  In
+        case  S[i,j]  is  non-existent,  it  is  inserted  in  the  table.  Table
+        automatically grows when necessary.
+
+        INPUT PARAMETERS
+            S           -   sparse M*N matrix in Hash-Table representation.
+                            Exception will be thrown for CRS matrix.
+            I           -   row index of the element to modify, 0<=I<M
+            J           -   column index of the element to modify, 0<=J<N
+            V           -   value to add, must be finite number
+
+        OUTPUT PARAMETERS
+            S           -   modified matrix
+            
+        NOTE 1:  when  S[i,j]  is exactly zero after modification, it is  deleted
+        from the table.
+
+          -- ALGLIB PROJECT --
+             Copyright 14.10.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sparseadd(sparsematrix s,
+            int i,
+            int j,
+            double v)
+        {
+            int hashcode = 0;
+            int tcode = 0;
+            int k = 0;
+
+            alglib.ap.assert(s.matrixtype==0, "SparseAdd: matrix must be in the Hash-Table mode to do this operation");
+            alglib.ap.assert(i>=0, "SparseAdd: I<0");
+            alglib.ap.assert(i<s.m, "SparseAdd: I>=M");
+            alglib.ap.assert(j>=0, "SparseAdd: J<0");
+            alglib.ap.assert(j<s.n, "SparseAdd: J>=N");
+            alglib.ap.assert(math.isfinite(v), "SparseAdd: V is not finite number");
+            if( (double)(v)==(double)(0) )
+            {
+                return;
+            }
+            tcode = -1;
+            k = alglib.ap.len(s.vals);
+            if( (double)((1-maxloadfactor)*k)>=(double)(s.nfree) )
+            {
+                sparseresizematrix(s);
+                k = alglib.ap.len(s.vals);
+            }
+            hashcode = hash(i, j, k);
+            while( true )
+            {
+                if( s.idx[2*hashcode]==-1 )
+                {
+                    if( tcode!=-1 )
+                    {
+                        hashcode = tcode;
+                    }
+                    s.vals[hashcode] = v;
+                    s.idx[2*hashcode] = i;
+                    s.idx[2*hashcode+1] = j;
+                    if( tcode==-1 )
+                    {
+                        s.nfree = s.nfree-1;
+                    }
+                    return;
+                }
+                else
+                {
+                    if( s.idx[2*hashcode]==i && s.idx[2*hashcode+1]==j )
+                    {
+                        s.vals[hashcode] = s.vals[hashcode]+v;
+                        if( (double)(s.vals[hashcode])==(double)(0) )
+                        {
+                            s.idx[2*hashcode] = -2;
+                        }
+                        return;
+                    }
+                    
+                    //
+                    //is it deleted element?
+                    //
+                    if( tcode==-1 && s.idx[2*hashcode]==-2 )
+                    {
+                        tcode = hashcode;
+                    }
+                    
+                    //
+                    //next step
+                    //
+                    hashcode = (hashcode+1)%k;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This function modifies S[i,j] - element of the sparse matrix. Matrix must
+        be in a Hash-Table mode.
+
+        In  case  new  value  of S[i,j] is zero, this element is deleted from the 
+        table.
+
+        INPUT PARAMETERS
+            S           -   sparse M*N matrix in Hash-Table representation.
+                            Exception will be thrown for CRS matrix.
+            I           -   row index of the element to modify, 0<=I<M
+            J           -   column index of the element to modify, 0<=J<N
+            V           -   value to set, must be finite number, can be zero
+
+        OUTPUT PARAMETERS
+            S           -   modified matrix
+            
+        NOTE:  this  function  has  no  effect  when  called with zero V for non-
+        existent element.
+
+          -- ALGLIB PROJECT --
+             Copyright 14.10.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sparseset(sparsematrix s,
+            int i,
+            int j,
+            double v)
+        {
+            int hashcode = 0;
+            int tcode = 0;
+            int k = 0;
+
+            alglib.ap.assert(i>=0, "SparseSet: I<0");
+            alglib.ap.assert(i<s.m, "SparseSet: I>=M");
+            alglib.ap.assert(j>=0, "SparseSet: J<0");
+            alglib.ap.assert(j<s.n, "SparseSet: J>=N");
+            alglib.ap.assert(math.isfinite(v), "SparseSet: V is not finite number");
+            
+            //
+            // Hash-table matrix
+            //
+            if( s.matrixtype==0 )
+            {
+                tcode = -1;
+                k = alglib.ap.len(s.vals);
+                if( (double)((1-maxloadfactor)*k)>=(double)(s.nfree) )
+                {
+                    sparseresizematrix(s);
+                    k = alglib.ap.len(s.vals);
+                }
+                hashcode = hash(i, j, k);
+                while( true )
+                {
+                    if( s.idx[2*hashcode]==-1 )
+                    {
+                        if( (double)(v)!=(double)(0) )
+                        {
+                            if( tcode!=-1 )
+                            {
+                                hashcode = tcode;
+                            }
+                            s.vals[hashcode] = v;
+                            s.idx[2*hashcode] = i;
+                            s.idx[2*hashcode+1] = j;
+                            if( tcode==-1 )
+                            {
+                                s.nfree = s.nfree-1;
+                            }
+                        }
+                        return;
+                    }
+                    else
+                    {
+                        if( s.idx[2*hashcode]==i && s.idx[2*hashcode+1]==j )
+                        {
+                            if( (double)(v)==(double)(0) )
+                            {
+                                s.idx[2*hashcode] = -2;
+                            }
+                            else
+                            {
+                                s.vals[hashcode] = v;
+                            }
+                            return;
+                        }
+                        if( tcode==-1 && s.idx[2*hashcode]==-2 )
+                        {
+                            tcode = hashcode;
+                        }
+                        
+                        //
+                        // next step
+                        //
+                        hashcode = (hashcode+1)%k;
+                    }
+                }
+            }
+            
+            //
+            // CRS matrix
+            //
+            if( s.matrixtype==1 )
+            {
+                alglib.ap.assert((double)(v)!=(double)(0), "SparseSet: CRS format does not allow you to write zero elements");
+                alglib.ap.assert(s.ridx[i]<=s.ninitialized, "SparseSet: too few initialized elements at some row (you have promised more when called SparceCreateCRS)");
+                alglib.ap.assert(s.ridx[i+1]>s.ninitialized, "SparseSet: too many initialized elements at some row (you have promised less when called SparceCreateCRS)");
+                alglib.ap.assert(s.ninitialized==s.ridx[i] || s.idx[s.ninitialized-1]<j, "SparseSet: incorrect column order (you must fill every row from left to right)");
+                s.vals[s.ninitialized] = v;
+                s.idx[s.ninitialized] = j;
+                s.ninitialized = s.ninitialized+1;
+                
+                //
+                //if matrix has been created then
+                //initiale 'S.UIdx' and 'S.DIdx'
+                //
+                if( s.ninitialized==s.ridx[s.m] )
+                {
+                    sparseinitduidx(s);
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This function returns S[i,j] - element of the sparse matrix.  Matrix  can
+        be in any mode (Hash-Table or CRS), but this function is  less  efficient
+        for CRS matrices.  Hash-Table  matrices can  find element  in O(1)  time, 
+        while  CRS  matrices  need  O(RS) time, where RS is an number of non-zero
+        elements in a row.
+
+        INPUT PARAMETERS
+            S           -   sparse M*N matrix in Hash-Table representation.
+                            Exception will be thrown for CRS matrix.
+            I           -   row index of the element to modify, 0<=I<M
+            J           -   column index of the element to modify, 0<=J<N
+
+        RESULT
+            value of S[I,J] or zero (in case no element with such index is found)
+
+          -- ALGLIB PROJECT --
+             Copyright 14.10.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static double sparseget(sparsematrix s,
+            int i,
+            int j)
+        {
+            double result = 0;
+            int hashcode = 0;
+            int k = 0;
+            int k0 = 0;
+            int k1 = 0;
+
+            alglib.ap.assert(i>=0, "SparseGet: I<0");
+            alglib.ap.assert(i<s.m, "SparseGet: I>=M");
+            alglib.ap.assert(j>=0, "SparseGet: J<0");
+            alglib.ap.assert(j<s.n, "SparseGet: J>=N");
+            k = alglib.ap.len(s.vals);
+            result = 0;
+            if( s.matrixtype==0 )
+            {
+                hashcode = hash(i, j, k);
+                while( true )
+                {
+                    if( s.idx[2*hashcode]==-1 )
+                    {
+                        return result;
+                    }
+                    if( s.idx[2*hashcode]==i && s.idx[2*hashcode+1]==j )
+                    {
+                        result = s.vals[hashcode];
+                        return result;
+                    }
+                    hashcode = (hashcode+1)%k;
+                }
+            }
+            if( s.matrixtype==1 )
+            {
+                alglib.ap.assert(s.ninitialized==s.ridx[s.m], "SparseGet: some rows/elements of the CRS matrix were not initialized (you must initialize everything you promised to SparseCreateCRS)");
+                k0 = s.ridx[i];
+                k1 = s.ridx[i+1]-1;
+                for(k=k0; k<=k1; k++)
+                {
+                    if( s.idx[k]==j )
+                    {
+                        result = s.vals[k];
+                        return result;
+                    }
+                }
+                return result;
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function converts matrix to CRS format.
+
+        Some  algorithms  (linear  algebra ones, for example) require matrices in
+        CRS format.
+
+        INPUT PARAMETERS
+            S           -   sparse M*N matrix.
+
+        OUTPUT PARAMETERS
+            S           -   matrix in CRS format
+            
+        NOTE:  this  function  has  no  effect  when  called with matrix which is 
+        already in CRS mode.
+
+          -- ALGLIB PROJECT --
+             Copyright 14.10.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sparseconverttocrs(sparsematrix s)
+        {
+            int i = 0;
+            double[] tvals = new double[0];
+            int[] tidx = new int[0];
+            int[] temp = new int[0];
+            int nonne = 0;
+            int k = 0;
+
+            alglib.ap.assert(s.matrixtype==0 || s.matrixtype==1, "SparseConvertToCRS: invalid matrix type");
+            if( s.matrixtype==1 )
+            {
+                return;
+            }
+            s.matrixtype = 1;
+            nonne = 0;
+            k = alglib.ap.len(s.vals);
+            alglib.ap.swap(ref s.vals, ref tvals);
+            alglib.ap.swap(ref s.idx, ref tidx);
+            s.ridx = new int[s.m+1];
+            for(i=0; i<=s.m; i++)
+            {
+                s.ridx[i] = 0;
+            }
+            temp = new int[s.m];
+            for(i=0; i<=s.m-1; i++)
+            {
+                temp[i] = 0;
+            }
+            
+            //
+            // Number of elements per row
+            //
+            for(i=0; i<=k-1; i++)
+            {
+                if( tidx[2*i]>=0 )
+                {
+                    s.ridx[tidx[2*i]+1] = s.ridx[tidx[2*i]+1]+1;
+                    nonne = nonne+1;
+                }
+            }
+            
+            //
+            // Fill RIdx (offsets of rows)
+            //
+            for(i=0; i<=s.m-1; i++)
+            {
+                s.ridx[i+1] = s.ridx[i+1]+s.ridx[i];
+            }
+            
+            //
+            // Allocate memory
+            //
+            s.vals = new double[nonne];
+            s.idx = new int[nonne];
+            for(i=0; i<=k-1; i++)
+            {
+                if( tidx[2*i]>=0 )
+                {
+                    s.vals[s.ridx[tidx[2*i]]+temp[tidx[2*i]]] = tvals[i];
+                    s.idx[s.ridx[tidx[2*i]]+temp[tidx[2*i]]] = tidx[2*i+1];
+                    temp[tidx[2*i]] = temp[tidx[2*i]]+1;
+                }
+            }
+            
+            //
+            // Set NInitialized
+            //
+            s.ninitialized = s.ridx[s.m];
+            
+            //
+            //sorting of elements
+            //
+            for(i=0; i<=s.m-1; i++)
+            {
+                tsort.tagsortmiddleir(ref s.idx, ref s.vals, s.ridx[i], s.ridx[i+1]-s.ridx[i]);
+            }
+            
+            //
+            //initialization 'S.UIdx' and 'S.DIdx'
+            //
+            sparseinitduidx(s);
+        }
+
+
+        /*************************************************************************
+        This function calculates matrix-vector product  S*x.  Matrix  S  must  be
+        stored in CRS format (exception will be thrown otherwise).
+
+        INPUT PARAMETERS
+            S           -   sparse M*N matrix in CRS format (you MUST convert  it
+                            to CRS before calling this function).
+            X           -   array[N], input vector. For  performance  reasons  we 
+                            make only quick checks - we check that array size  is
+                            at least N, but we do not check for NAN's or INF's.
+            Y           -   output buffer, possibly preallocated. In case  buffer
+                            size is too small to store  result,  this  buffer  is
+                            automatically resized.
+            
+        OUTPUT PARAMETERS
+            Y           -   array[M], S*x
+            
+        NOTE: this function throws exception when called for non-CRS matrix.  You
+        must convert your matrix  with  SparseConvertToCRS()  before  using  this
+        function.
+
+          -- ALGLIB PROJECT --
+             Copyright 14.10.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sparsemv(sparsematrix s,
+            double[] x,
+            ref double[] y)
+        {
+            double tval = 0;
+            int i = 0;
+            int j = 0;
+            int lt = 0;
+            int rt = 0;
+
+            alglib.ap.assert(s.matrixtype==1, "SparseMV: incorrect matrix type (convert your matrix to CRS)");
+            alglib.ap.assert(s.ninitialized==s.ridx[s.m], "SparseMV: some rows/elements of the CRS matrix were not initialized (you must initialize everything you promised to SparseCreateCRS)");
+            alglib.ap.assert(alglib.ap.len(x)>=s.n, "SparseMV: length(X)<N");
+            apserv.rvectorsetlengthatleast(ref y, s.m);
+            for(i=0; i<=s.m-1; i++)
+            {
+                tval = 0;
+                lt = s.ridx[i];
+                rt = s.ridx[i+1];
+                for(j=lt; j<=rt-1; j++)
+                {
+                    tval = tval+x[s.idx[j]]*s.vals[j];
+                }
+                y[i] = tval;
+            }
+        }
+
+
+        /*************************************************************************
+        This function calculates matrix-vector product  S^T*x. Matrix S  must  be
+        stored in CRS format (exception will be thrown otherwise).
+
+        INPUT PARAMETERS
+            S           -   sparse M*N matrix in CRS format (you MUST convert  it
+                            to CRS before calling this function).
+            X           -   array[M], input vector. For  performance  reasons  we 
+                            make only quick checks - we check that array size  is
+                            at least M, but we do not check for NAN's or INF's.
+            Y           -   output buffer, possibly preallocated. In case  buffer
+                            size is too small to store  result,  this  buffer  is
+                            automatically resized.
+            
+        OUTPUT PARAMETERS
+            Y           -   array[N], S^T*x
+            
+        NOTE: this function throws exception when called for non-CRS matrix.  You
+        must convert your matrix  with  SparseConvertToCRS()  before  using  this
+        function.
+
+          -- ALGLIB PROJECT --
+             Copyright 14.10.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sparsemtv(sparsematrix s,
+            double[] x,
+            ref double[] y)
+        {
+            int i = 0;
+            int j = 0;
+            int lt = 0;
+            int rt = 0;
+            int ct = 0;
+            double v = 0;
+
+            alglib.ap.assert(s.matrixtype==1, "SparseMTV: incorrect matrix type (convert your matrix to CRS)");
+            alglib.ap.assert(s.ninitialized==s.ridx[s.m], "SparseMTV: some rows/elements of the CRS matrix were not initialized (you must initialize everything you promised to SparseCreateCRS)");
+            alglib.ap.assert(alglib.ap.len(x)>=s.m, "SparseMTV: Length(X)<M");
+            apserv.rvectorsetlengthatleast(ref y, s.n);
+            for(i=0; i<=s.n-1; i++)
+            {
+                y[i] = 0;
+            }
+            for(i=0; i<=s.m-1; i++)
+            {
+                lt = s.ridx[i];
+                rt = s.ridx[i+1];
+                v = x[i];
+                for(j=lt; j<=rt-1; j++)
+                {
+                    ct = s.idx[j];
+                    y[ct] = y[ct]+v*s.vals[j];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This function simultaneously calculates two matrix-vector products:
+            S*x and S^T*x.
+        S must be square (non-rectangular) matrix stored in CRS format (exception  
+        will be thrown otherwise).
+
+        INPUT PARAMETERS
+            S           -   sparse N*N matrix in CRS format (you MUST convert  it
+                            to CRS before calling this function).
+            X           -   array[N], input vector. For  performance  reasons  we 
+                            make only quick checks - we check that array size  is
+                            at least N, but we do not check for NAN's or INF's.
+            Y0          -   output buffer, possibly preallocated. In case  buffer
+                            size is too small to store  result,  this  buffer  is
+                            automatically resized.
+            Y1          -   output buffer, possibly preallocated. In case  buffer
+                            size is too small to store  result,  this  buffer  is
+                            automatically resized.
+            
+        OUTPUT PARAMETERS
+            Y0          -   array[N], S*x
+            Y1          -   array[N], S^T*x
+            
+        NOTE: this function throws exception when called for non-CRS matrix.  You
+        must convert your matrix  with  SparseConvertToCRS()  before  using  this
+        function. It also throws exception when S is non-square.
+
+          -- ALGLIB PROJECT --
+             Copyright 14.10.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sparsemv2(sparsematrix s,
+            double[] x,
+            ref double[] y0,
+            ref double[] y1)
+        {
+            int l = 0;
+            double tval = 0;
+            int i = 0;
+            int j = 0;
+            double vx = 0;
+            double vs = 0;
+            int vi = 0;
+            int j0 = 0;
+            int j1 = 0;
+
+            alglib.ap.assert(s.matrixtype==1, "SparseMV2: incorrect matrix type (convert your matrix to CRS)");
+            alglib.ap.assert(s.ninitialized==s.ridx[s.m], "SparseMV: some rows/elements of the CRS matrix were not initialized (you must initialize everything you promised to SparseCreateCRS)");
+            alglib.ap.assert(s.m==s.n, "SparseMV2: matrix is non-square");
+            l = alglib.ap.len(x);
+            alglib.ap.assert(l>=s.n, "SparseMV2: Length(X)<N");
+            apserv.rvectorsetlengthatleast(ref y0, l);
+            apserv.rvectorsetlengthatleast(ref y1, l);
+            for(i=0; i<=s.n-1; i++)
+            {
+                y1[i] = 0;
+            }
+            for(i=0; i<=s.m-1; i++)
+            {
+                tval = 0;
+                vx = x[i];
+                j0 = s.ridx[i];
+                j1 = s.ridx[i+1]-1;
+                for(j=j0; j<=j1; j++)
+                {
+                    vi = s.idx[j];
+                    vs = s.vals[j];
+                    tval = tval+x[vi]*vs;
+                    y1[vi] = y1[vi]+vx*vs;
+                }
+                y0[i] = tval;
+            }
+        }
+
+
+        /*************************************************************************
+        This function calculates matrix-vector product  S*x, when S is  symmetric
+        matrix.  Matrix  S  must  be stored in  CRS  format  (exception  will  be
+        thrown otherwise).
+
+        INPUT PARAMETERS
+            S           -   sparse M*M matrix in CRS format (you MUST convert  it
+                            to CRS before calling this function).
+            X           -   array[N], input vector. For  performance  reasons  we 
+                            make only quick checks - we check that array size  is
+                            at least N, but we do not check for NAN's or INF's.
+            Y           -   output buffer, possibly preallocated. In case  buffer
+                            size is too small to store  result,  this  buffer  is
+                            automatically resized.
+            
+        OUTPUT PARAMETERS
+            Y           -   array[M], S*x
+            
+        NOTE: this function throws exception when called for non-CRS matrix.  You
+        must convert your matrix  with  SparseConvertToCRS()  before  using  this
+        function.
+
+          -- ALGLIB PROJECT --
+             Copyright 14.10.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sparsesmv(sparsematrix s,
+            bool isupper,
+            double[] x,
+            ref double[] y)
+        {
+            int i = 0;
+            int j = 0;
+            int id = 0;
+            int lt = 0;
+            int rt = 0;
+            double v = 0;
+            double vy = 0;
+            double vx = 0;
+
+            alglib.ap.assert(s.matrixtype==1, "SparseSMV: incorrect matrix type (convert your matrix to CRS)");
+            alglib.ap.assert(s.ninitialized==s.ridx[s.m], "SparseSMV: some rows/elements of the CRS matrix were not initialized (you must initialize everything you promised to SparseCreateCRS)");
+            alglib.ap.assert(alglib.ap.len(x)>=s.n, "SparseSMV: length(X)<N");
+            alglib.ap.assert(s.m==s.n, "SparseSMV: non-square matrix");
+            apserv.rvectorsetlengthatleast(ref y, s.m);
+            for(i=0; i<=s.m-1; i++)
+            {
+                y[i] = 0;
+            }
+            for(i=0; i<=s.m-1; i++)
+            {
+                if( s.didx[i]!=s.uidx[i] )
+                {
+                    y[i] = y[i]+s.vals[s.didx[i]]*x[s.idx[s.didx[i]]];
+                }
+                if( isupper )
+                {
+                    lt = s.uidx[i];
+                    rt = s.ridx[i+1];
+                    vy = 0;
+                    vx = x[i];
+                    for(j=lt; j<=rt-1; j++)
+                    {
+                        id = s.idx[j];
+                        v = s.vals[j];
+                        vy = vy+x[id]*v;
+                        y[id] = y[id]+vx*v;
+                    }
+                    y[i] = y[i]+vy;
+                }
+                else
+                {
+                    lt = s.ridx[i];
+                    rt = s.didx[i];
+                    vy = 0;
+                    vx = x[i];
+                    for(j=lt; j<=rt-1; j++)
+                    {
+                        id = s.idx[j];
+                        v = s.vals[j];
+                        vy = vy+x[id]*v;
+                        y[id] = y[id]+vx*v;
+                    }
+                    y[i] = y[i]+vy;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This function calculates matrix-matrix product  S*A.  Matrix  S  must  be
+        stored in CRS format (exception will be thrown otherwise).
+
+        INPUT PARAMETERS
+            S           -   sparse M*N matrix in CRS format (you MUST convert  it
+                            to CRS before calling this function).
+            A           -   array[N][K], input dense matrix. For  performance reasons
+                            we make only quick checks - we check that array size  
+                            is at least N, but we do not check for NAN's or INF's.
+            K           -   number of columns of matrix (A).
+            B           -   output buffer, possibly preallocated. In case  buffer
+                            size is too small to store  result,  this  buffer  is
+                            automatically resized.
+            
+        OUTPUT PARAMETERS
+            B           -   array[M][K], S*A
+            
+        NOTE: this function throws exception when called for non-CRS matrix.  You
+        must convert your matrix  with  SparseConvertToCRS()  before  using  this
+        function.
+
+          -- ALGLIB PROJECT --
+             Copyright 14.10.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sparsemm(sparsematrix s,
+            double[,] a,
+            int k,
+            ref double[,] b)
+        {
+            double tval = 0;
+            double v = 0;
+            int id = 0;
+            int i = 0;
+            int j = 0;
+            int k0 = 0;
+            int lt = 0;
+            int rt = 0;
+            int i_ = 0;
+
+            alglib.ap.assert(s.matrixtype==1, "SparseMV: incorrect matrix type (convert your matrix to CRS)");
+            alglib.ap.assert(s.ninitialized==s.ridx[s.m], "SparseMV: some rows/elements of the CRS matrix were not initialized (you must initialize everything you promised to SparseCreateCRS)");
+            alglib.ap.assert(alglib.ap.rows(a)>=s.n, "SparseMV: Rows(A)<N");
+            alglib.ap.assert(k>0, "SparseMV: K<=0");
+            apserv.rmatrixsetlengthatleast(ref b, s.m, k);
+            if( k<linalgswitch )
+            {
+                for(i=0; i<=s.m-1; i++)
+                {
+                    for(j=0; j<=k-1; j++)
+                    {
+                        tval = 0;
+                        lt = s.ridx[i];
+                        rt = s.ridx[i+1];
+                        for(k0=lt; k0<=rt-1; k0++)
+                        {
+                            tval = tval+s.vals[k0]*a[s.idx[k0],j];
+                        }
+                        b[i,j] = tval;
+                    }
+                }
+            }
+            else
+            {
+                for(i=0; i<=s.m-1; i++)
+                {
+                    for(j=0; j<=k-1; j++)
+                    {
+                        b[i,j] = 0;
+                    }
+                }
+                for(i=0; i<=s.m-1; i++)
+                {
+                    lt = s.ridx[i];
+                    rt = s.ridx[i+1];
+                    for(j=lt; j<=rt-1; j++)
+                    {
+                        id = s.idx[j];
+                        v = s.vals[j];
+                        for(i_=0; i_<=k-1;i_++)
+                        {
+                            b[i,i_] = b[i,i_] + v*a[id,i_];
+                        }
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This function calculates matrix-matrix product  S^T*A. Matrix S  must  be
+        stored in CRS format (exception will be thrown otherwise).
+
+        INPUT PARAMETERS
+            S           -   sparse M*N matrix in CRS format (you MUST convert  it
+                            to CRS before calling this function).
+            A           -   array[M][K], input dense matrix. For performance reasons
+                            we make only quick checks - we check that array size  is
+                            at least M, but we do not check for NAN's or INF's.
+            K           -   number of columns of matrix (A).                    
+            B           -   output buffer, possibly preallocated. In case  buffer
+                            size is too small to store  result,  this  buffer  is
+                            automatically resized.
+            
+        OUTPUT PARAMETERS
+            B           -   array[N][K], S^T*A
+            
+        NOTE: this function throws exception when called for non-CRS matrix.  You
+        must convert your matrix  with  SparseConvertToCRS()  before  using  this
+        function.
+
+          -- ALGLIB PROJECT --
+             Copyright 14.10.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sparsemtm(sparsematrix s,
+            double[,] a,
+            int k,
+            ref double[,] b)
+        {
+            int i = 0;
+            int j = 0;
+            int k0 = 0;
+            int lt = 0;
+            int rt = 0;
+            int ct = 0;
+            double v = 0;
+            int i_ = 0;
+
+            alglib.ap.assert(s.matrixtype==1, "SparseMTM: incorrect matrix type (convert your matrix to CRS)");
+            alglib.ap.assert(s.ninitialized==s.ridx[s.m], "SparseMTM: some rows/elements of the CRS matrix were not initialized (you must initialize everything you promised to SparseCreateCRS)");
+            alglib.ap.assert(alglib.ap.rows(a)>=s.m, "SparseMTM: Rows(A)<M");
+            alglib.ap.assert(k>0, "SparseMTM: K<=0");
+            apserv.rmatrixsetlengthatleast(ref b, s.n, k);
+            for(i=0; i<=s.n-1; i++)
+            {
+                for(j=0; j<=k-1; j++)
+                {
+                    b[i,j] = 0;
+                }
+            }
+            if( k<linalgswitch )
+            {
+                for(i=0; i<=s.m-1; i++)
+                {
+                    lt = s.ridx[i];
+                    rt = s.ridx[i+1];
+                    for(k0=lt; k0<=rt-1; k0++)
+                    {
+                        v = s.vals[k0];
+                        ct = s.idx[k0];
+                        for(j=0; j<=k-1; j++)
+                        {
+                            b[ct,j] = b[ct,j]+v*a[i,j];
+                        }
+                    }
+                }
+            }
+            else
+            {
+                for(i=0; i<=s.m-1; i++)
+                {
+                    lt = s.ridx[i];
+                    rt = s.ridx[i+1];
+                    for(j=lt; j<=rt-1; j++)
+                    {
+                        v = s.vals[j];
+                        ct = s.idx[j];
+                        for(i_=0; i_<=k-1;i_++)
+                        {
+                            b[ct,i_] = b[ct,i_] + v*a[i,i_];
+                        }
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This function simultaneously calculates two matrix-matrix products:
+            S*A and S^T*A.
+        S must be square (non-rectangular) matrix stored in CRS format (exception  
+        will be thrown otherwise).
+
+        INPUT PARAMETERS
+            S           -   sparse N*N matrix in CRS format (you MUST convert  it
+                            to CRS before calling this function).
+            A           -   array[N][K], input dense matrix. For performance reasons
+                            we make only quick checks - we check that array size  is
+                            at least N, but we do not check for NAN's or INF's.
+            K           -   number of columns of matrix (A).                    
+            B0          -   output buffer, possibly preallocated. In case  buffer
+                            size is too small to store  result,  this  buffer  is
+                            automatically resized.
+            B1          -   output buffer, possibly preallocated. In case  buffer
+                            size is too small to store  result,  this  buffer  is
+                            automatically resized.
+            
+        OUTPUT PARAMETERS
+            B0          -   array[N][K], S*A
+            B1          -   array[N][K], S^T*A
+            
+        NOTE: this function throws exception when called for non-CRS matrix.  You
+        must convert your matrix  with  SparseConvertToCRS()  before  using  this
+        function. It also throws exception when S is non-square.
+
+          -- ALGLIB PROJECT --
+             Copyright 14.10.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sparsemm2(sparsematrix s,
+            double[,] a,
+            int k,
+            ref double[,] b0,
+            ref double[,] b1)
+        {
+            int i = 0;
+            int j = 0;
+            int k0 = 0;
+            int lt = 0;
+            int rt = 0;
+            int ct = 0;
+            double v = 0;
+            double tval = 0;
+            int i_ = 0;
+
+            alglib.ap.assert(s.matrixtype==1, "SparseMM2: incorrect matrix type (convert your matrix to CRS)");
+            alglib.ap.assert(s.ninitialized==s.ridx[s.m], "SparseMM2: some rows/elements of the CRS matrix were not initialized (you must initialize everything you promised to SparseCreateCRS)");
+            alglib.ap.assert(s.m==s.n, "SparseMM2: matrix is non-square");
+            alglib.ap.assert(alglib.ap.rows(a)>=s.n, "SparseMM2: Rows(A)<N");
+            alglib.ap.assert(k>0, "SparseMM2: K<=0");
+            apserv.rmatrixsetlengthatleast(ref b0, s.m, k);
+            apserv.rmatrixsetlengthatleast(ref b1, s.n, k);
+            for(i=0; i<=s.n-1; i++)
+            {
+                for(j=0; j<=k-1; j++)
+                {
+                    b1[i,j] = 0;
+                }
+            }
+            if( k<linalgswitch )
+            {
+                for(i=0; i<=s.m-1; i++)
+                {
+                    for(j=0; j<=k-1; j++)
+                    {
+                        tval = 0;
+                        lt = s.ridx[i];
+                        rt = s.ridx[i+1];
+                        v = a[i,j];
+                        for(k0=lt; k0<=rt-1; k0++)
+                        {
+                            ct = s.idx[k0];
+                            b1[ct,j] = b1[ct,j]+s.vals[k0]*v;
+                            tval = tval+s.vals[k0]*a[ct,j];
+                        }
+                        b0[i,j] = tval;
+                    }
+                }
+            }
+            else
+            {
+                for(i=0; i<=s.m-1; i++)
+                {
+                    for(j=0; j<=k-1; j++)
+                    {
+                        b0[i,j] = 0;
+                    }
+                }
+                for(i=0; i<=s.m-1; i++)
+                {
+                    lt = s.ridx[i];
+                    rt = s.ridx[i+1];
+                    for(j=lt; j<=rt-1; j++)
+                    {
+                        v = s.vals[j];
+                        ct = s.idx[j];
+                        for(i_=0; i_<=k-1;i_++)
+                        {
+                            b0[i,i_] = b0[i,i_] + v*a[ct,i_];
+                        }
+                        for(i_=0; i_<=k-1;i_++)
+                        {
+                            b1[ct,i_] = b1[ct,i_] + v*a[i,i_];
+                        }
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This function calculates matrix-matrix product  S*A, when S  is  symmetric
+        matrix.  Matrix  S  must  be stored  in  CRS  format  (exception  will  be
+        thrown otherwise).
+
+        INPUT PARAMETERS
+            S           -   sparse M*M matrix in CRS format (you MUST convert  it
+                            to CRS before calling this function).
+            A           -   array[N][K], input dense matrix. For performance reasons
+                            we make only quick checks - we check that array size is
+                            at least N, but we do not check for NAN's or INF's.
+            K           -   number of columns of matrix (A).  
+            B           -   output buffer, possibly preallocated. In case  buffer
+                            size is too small to store  result,  this  buffer  is
+                            automatically resized.
+            
+        OUTPUT PARAMETERS
+            B           -   array[M][K], S*A
+            
+        NOTE: this function throws exception when called for non-CRS matrix.  You
+        must convert your matrix  with  SparseConvertToCRS()  before  using  this
+        function.
+
+          -- ALGLIB PROJECT --
+             Copyright 14.10.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sparsesmm(sparsematrix s,
+            bool isupper,
+            double[,] a,
+            int k,
+            ref double[,] b)
+        {
+            int i = 0;
+            int j = 0;
+            int k0 = 0;
+            int id = 0;
+            int lt = 0;
+            int rt = 0;
+            double v = 0;
+            double vb = 0;
+            double va = 0;
+            int i_ = 0;
+
+            alglib.ap.assert(s.matrixtype==1, "SparseSMM: incorrect matrix type (convert your matrix to CRS)");
+            alglib.ap.assert(s.ninitialized==s.ridx[s.m], "SparseSMM: some rows/elements of the CRS matrix were not initialized (you must initialize everything you promised to SparseCreateCRS)");
+            alglib.ap.assert(alglib.ap.rows(a)>=s.n, "SparseSMM: Rows(X)<N");
+            alglib.ap.assert(s.m==s.n, "SparseSMM: matrix is non-square");
+            apserv.rmatrixsetlengthatleast(ref b, s.m, k);
+            for(i=0; i<=s.m-1; i++)
+            {
+                for(j=0; j<=k-1; j++)
+                {
+                    b[i,j] = 0;
+                }
+            }
+            if( k>linalgswitch )
+            {
+                for(i=0; i<=s.m-1; i++)
+                {
+                    for(j=0; j<=k-1; j++)
+                    {
+                        if( s.didx[i]!=s.uidx[i] )
+                        {
+                            id = s.didx[i];
+                            b[i,j] = b[i,j]+s.vals[id]*a[s.idx[id],j];
+                        }
+                        if( isupper )
+                        {
+                            lt = s.uidx[i];
+                            rt = s.ridx[i+1];
+                            vb = 0;
+                            va = a[i,j];
+                            for(k0=lt; k0<=rt-1; k0++)
+                            {
+                                id = s.idx[k0];
+                                v = s.vals[k0];
+                                vb = vb+a[id,j]*v;
+                                b[id,j] = b[id,j]+va*v;
+                            }
+                            b[i,j] = b[i,j]+vb;
+                        }
+                        else
+                        {
+                            lt = s.ridx[i];
+                            rt = s.didx[i];
+                            vb = 0;
+                            va = a[i,j];
+                            for(k0=lt; k0<=rt-1; k0++)
+                            {
+                                id = s.idx[k0];
+                                v = s.vals[k0];
+                                vb = vb+a[id,j]*v;
+                                b[id,j] = b[id,j]+va*v;
+                            }
+                            b[i,j] = b[i,j]+vb;
+                        }
+                    }
+                }
+            }
+            else
+            {
+                for(i=0; i<=s.m-1; i++)
+                {
+                    if( s.didx[i]!=s.uidx[i] )
+                    {
+                        id = s.didx[i];
+                        v = s.vals[id];
+                        for(i_=0; i_<=k-1;i_++)
+                        {
+                            b[i,i_] = b[i,i_] + v*a[s.idx[id],i_];
+                        }
+                    }
+                    if( isupper )
+                    {
+                        lt = s.uidx[i];
+                        rt = s.ridx[i+1];
+                        for(j=lt; j<=rt-1; j++)
+                        {
+                            id = s.idx[j];
+                            v = s.vals[j];
+                            for(i_=0; i_<=k-1;i_++)
+                            {
+                                b[i,i_] = b[i,i_] + v*a[id,i_];
+                            }
+                            for(i_=0; i_<=k-1;i_++)
+                            {
+                                b[id,i_] = b[id,i_] + v*a[i,i_];
+                            }
+                        }
+                    }
+                    else
+                    {
+                        lt = s.ridx[i];
+                        rt = s.didx[i];
+                        for(j=lt; j<=rt-1; j++)
+                        {
+                            id = s.idx[j];
+                            v = s.vals[j];
+                            for(i_=0; i_<=k-1;i_++)
+                            {
+                                b[i,i_] = b[i,i_] + v*a[id,i_];
+                            }
+                            for(i_=0; i_<=k-1;i_++)
+                            {
+                                b[id,i_] = b[id,i_] + v*a[i,i_];
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This procedure resizes Hash-Table matrix. It can be called when you  have
+        deleted too many elements from the matrix, and you want to  free unneeded
+        memory.
+
+          -- ALGLIB PROJECT --
+             Copyright 14.10.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sparseresizematrix(sparsematrix s)
+        {
+            int k = 0;
+            int k1 = 0;
+            int i = 0;
+            double[] tvals = new double[0];
+            int[] tidx = new int[0];
+
+            alglib.ap.assert(s.matrixtype==0, "SparseResizeMatrix: incorrect matrix type");
+            
+            //
+            //initialization for length and number of non-null elementd
+            //
+            k = alglib.ap.len(s.vals);
+            k1 = 0;
+            
+            //
+            //calculating number of non-null elements
+            //
+            for(i=0; i<=k-1; i++)
+            {
+                if( s.idx[2*i]>=0 )
+                {
+                    k1 = k1+1;
+                }
+            }
+            
+            //
+            //initialization value for free space
+            //
+            s.nfree = (int)Math.Round(k1/desiredloadfactor*growfactor+additional)-k1;
+            tvals = new double[s.nfree+k1];
+            tidx = new int[2*(s.nfree+k1)];
+            alglib.ap.swap(ref s.vals, ref tvals);
+            alglib.ap.swap(ref s.idx, ref tidx);
+            for(i=0; i<=s.nfree+k1-1; i++)
+            {
+                s.idx[2*i] = -1;
+            }
+            for(i=0; i<=k-1; i++)
+            {
+                if( tidx[2*i]>=0 )
+                {
+                    sparseset(s, tidx[2*i], tidx[2*i+1], tvals[i]);
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This function return average length of chain at hash-table.
+
+          -- ALGLIB PROJECT --
+             Copyright 14.10.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static double sparsegetaveragelengthofchain(sparsematrix s)
+        {
+            double result = 0;
+            int nchains = 0;
+            int talc = 0;
+            int l = 0;
+            int i = 0;
+            int ind0 = 0;
+            int ind1 = 0;
+            int hashcode = 0;
+
+            
+            //
+            //if matrix represent in CRS then return zero and exit
+            //
+            if( s.matrixtype==1 )
+            {
+                result = 0;
+                return result;
+            }
+            nchains = 0;
+            talc = 0;
+            l = alglib.ap.len(s.vals);
+            for(i=0; i<=l-1; i++)
+            {
+                ind0 = 2*i;
+                if( s.idx[ind0]!=-1 )
+                {
+                    nchains = nchains+1;
+                    hashcode = hash(s.idx[ind0], s.idx[ind0+1], l);
+                    while( true )
+                    {
+                        talc = talc+1;
+                        ind1 = 2*hashcode;
+                        if( s.idx[ind0]==s.idx[ind1] && s.idx[ind0+1]==s.idx[ind1+1] )
+                        {
+                            break;
+                        }
+                        hashcode = (hashcode+1)%l;
+                    }
+                }
+            }
+            if( nchains==0 )
+            {
+                result = 0;
+            }
+            else
+            {
+                result = (double)talc/(double)nchains;
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        Procedure for initialization 'S.DIdx' and 'S.UIdx'
+
+
+          -- ALGLIB PROJECT --
+             Copyright 14.10.2011 by Bochkanov Sergey
+        *************************************************************************/
+        private static void sparseinitduidx(sparsematrix s)
+        {
+            int i = 0;
+            int j = 0;
+            int lt = 0;
+            int rt = 0;
+
+            s.didx = new int[s.m];
+            s.uidx = new int[s.m];
+            for(i=0; i<=s.m-1; i++)
+            {
+                s.uidx[i] = -1;
+                s.didx[i] = -1;
+                lt = s.ridx[i];
+                rt = s.ridx[i+1];
+                for(j=lt; j<=rt-1; j++)
+                {
+                    if( i<s.idx[j] && s.uidx[i]==-1 )
+                    {
+                        s.uidx[i] = j;
+                        break;
+                    }
+                    else
+                    {
+                        if( i==s.idx[j] )
+                        {
+                            s.didx[i] = j;
+                        }
+                    }
+                }
+                if( s.uidx[i]==-1 )
+                {
+                    s.uidx[i] = s.ridx[i+1];
+                }
+                if( s.didx[i]==-1 )
+                {
+                    s.didx[i] = s.uidx[i];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This is hash function.
+
+          -- ALGLIB PROJECT --
+             Copyright 14.10.2011 by Bochkanov Sergey
+        *************************************************************************/
+        private static int hash(int i,
+            int j,
+            int tabsize)
+        {
+            int result = 0;
+            hqrnd.hqrndstate r = new hqrnd.hqrndstate();
+
+            hqrnd.hqrndseed(i, j, r);
+            result = hqrnd.hqrnduniformi(r, tabsize);
+            return result;
+        }
+
+
+    }
+    public class normestimator
+    {
+        /*************************************************************************
+        This object stores state of the iterative norm estimation algorithm.
+
+        You should use ALGLIB functions to work with this object.
+        *************************************************************************/
+        public class normestimatorstate
+        {
+            public int n;
+            public int m;
+            public int nstart;
+            public int nits;
+            public int seedval;
+            public double[] x0;
+            public double[] x1;
+            public double[] t;
+            public double[] xbest;
+            public hqrnd.hqrndstate r;
+            public double[] x;
+            public double[] mv;
+            public double[] mtv;
+            public bool needmv;
+            public bool needmtv;
+            public double repnorm;
+            public rcommstate rstate;
+            public normestimatorstate()
+            {
+                x0 = new double[0];
+                x1 = new double[0];
+                t = new double[0];
+                xbest = new double[0];
+                r = new hqrnd.hqrndstate();
+                x = new double[0];
+                mv = new double[0];
+                mtv = new double[0];
+                rstate = new rcommstate();
+            }
+        };
+
+
+
+
+        /*************************************************************************
+        This procedure initializes matrix norm estimator.
+
+        USAGE:
+        1. User initializes algorithm state with NormEstimatorCreate() call
+        2. User calls NormEstimatorEstimateSparse() (or NormEstimatorIteration())
+        3. User calls NormEstimatorResults() to get solution.
+           
+        INPUT PARAMETERS:
+            M       -   number of rows in the matrix being estimated, M>0
+            N       -   number of columns in the matrix being estimated, N>0
+            NStart  -   number of random starting vectors
+                        recommended value - at least 5.
+            NIts    -   number of iterations to do with best starting vector
+                        recommended value - at least 5.
+
+        OUTPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+
+            
+        NOTE: this algorithm is effectively deterministic, i.e. it always  returns
+        same result when repeatedly called for the same matrix. In fact, algorithm
+        uses randomized starting vectors, but internal  random  numbers  generator
+        always generates same sequence of the random values (it is a  feature, not
+        bug).
+
+        Algorithm can be made non-deterministic with NormEstimatorSetSeed(0) call.
+
+          -- ALGLIB --
+             Copyright 06.12.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void normestimatorcreate(int m,
+            int n,
+            int nstart,
+            int nits,
+            normestimatorstate state)
+        {
+            alglib.ap.assert(m>0, "NormEstimatorCreate: M<=0");
+            alglib.ap.assert(n>0, "NormEstimatorCreate: N<=0");
+            alglib.ap.assert(nstart>0, "NormEstimatorCreate: NStart<=0");
+            alglib.ap.assert(nstart>0, "NormEstimatorCreate: NStart<=0");
+            state.m = m;
+            state.n = n;
+            state.nstart = nstart;
+            state.nits = nits;
+            state.seedval = 11;
+            hqrnd.hqrndrandomize(state.r);
+            state.x0 = new double[state.n];
+            state.t = new double[state.m];
+            state.x1 = new double[state.n];
+            state.xbest = new double[state.n];
+            state.x = new double[Math.Max(state.n, state.m)];
+            state.mv = new double[state.m];
+            state.mtv = new double[state.n];
+            state.rstate.ia = new int[3+1];
+            state.rstate.ra = new double[2+1];
+            state.rstate.stage = -1;
+        }
+
+
+        /*************************************************************************
+        This function changes seed value used by algorithm. In some cases we  need
+        deterministic processing, i.e. subsequent calls must return equal results,
+        in other cases we need non-deterministic algorithm which returns different
+        results for the same matrix on every pass.
+
+        Setting zero seed will lead to non-deterministic algorithm, while non-zero 
+        value will make our algorithm deterministic.
+
+        INPUT PARAMETERS:
+            State       -   norm estimator state, must be initialized with a  call
+                            to NormEstimatorCreate()
+            SeedVal     -   seed value, >=0. Zero value = non-deterministic algo.
+
+          -- ALGLIB --
+             Copyright 06.12.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void normestimatorsetseed(normestimatorstate state,
+            int seedval)
+        {
+            alglib.ap.assert(seedval>=0, "NormEstimatorSetSeed: SeedVal<0");
+            state.seedval = seedval;
+        }
+
+
+        /*************************************************************************
+
+          -- ALGLIB --
+             Copyright 06.12.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static bool normestimatoriteration(normestimatorstate state)
+        {
+            bool result = new bool();
+            int n = 0;
+            int m = 0;
+            int i = 0;
+            int itcnt = 0;
+            double v = 0;
+            double growth = 0;
+            double bestgrowth = 0;
+            int i_ = 0;
+
+            
+            //
+            // Reverse communication preparations
+            // I know it looks ugly, but it works the same way
+            // anywhere from C++ to Python.
+            //
+            // This code initializes locals by:
+            // * random values determined during code
+            //   generation - on first subroutine call
+            // * values from previous call - on subsequent calls
+            //
+            if( state.rstate.stage>=0 )
+            {
+                n = state.rstate.ia[0];
+                m = state.rstate.ia[1];
+                i = state.rstate.ia[2];
+                itcnt = state.rstate.ia[3];
+                v = state.rstate.ra[0];
+                growth = state.rstate.ra[1];
+                bestgrowth = state.rstate.ra[2];
+            }
+            else
+            {
+                n = -983;
+                m = -989;
+                i = -834;
+                itcnt = 900;
+                v = -287;
+                growth = 364;
+                bestgrowth = 214;
+            }
+            if( state.rstate.stage==0 )
+            {
+                goto lbl_0;
+            }
+            if( state.rstate.stage==1 )
+            {
+                goto lbl_1;
+            }
+            if( state.rstate.stage==2 )
+            {
+                goto lbl_2;
+            }
+            if( state.rstate.stage==3 )
+            {
+                goto lbl_3;
+            }
+            
+            //
+            // Routine body
+            //
+            n = state.n;
+            m = state.m;
+            if( state.seedval>0 )
+            {
+                hqrnd.hqrndseed(state.seedval, state.seedval+2, state.r);
+            }
+            bestgrowth = 0;
+            state.xbest[0] = 1;
+            for(i=1; i<=n-1; i++)
+            {
+                state.xbest[i] = 0;
+            }
+            itcnt = 0;
+        lbl_4:
+            if( itcnt>0 )
+            {
+                goto lbl_6;
+            }
+            do
+            {
+                v = 0;
+                for(i=0; i<=n-1; i++)
+                {
+                    state.x0[i] = hqrnd.hqrndnormal(state.r);
+                    v = v+math.sqr(state.x0[i]);
+                }
+            }
+            while( (double)(v)==(double)(0) );
+            v = 1/Math.Sqrt(v);
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x0[i_] = v*state.x0[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.x0[i_];
+            }
+            state.needmv = true;
+            state.needmtv = false;
+            state.rstate.stage = 0;
+            goto lbl_rcomm;
+        lbl_0:
+            for(i_=0; i_<=m-1;i_++)
+            {
+                state.x[i_] = state.mv[i_];
+            }
+            state.needmv = false;
+            state.needmtv = true;
+            state.rstate.stage = 1;
+            goto lbl_rcomm;
+        lbl_1:
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x1[i_] = state.mtv[i_];
+            }
+            v = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                v = v+math.sqr(state.x1[i]);
+            }
+            growth = Math.Sqrt(Math.Sqrt(v));
+            if( (double)(growth)>(double)(bestgrowth) )
+            {
+                v = 1/Math.Sqrt(v);
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    state.xbest[i_] = v*state.x1[i_];
+                }
+                bestgrowth = growth;
+            }
+            itcnt = itcnt+1;
+            goto lbl_4;
+        lbl_6:
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x0[i_] = state.xbest[i_];
+            }
+            itcnt = 0;
+        lbl_7:
+            if( itcnt>state.nits-1 )
+            {
+                goto lbl_9;
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.x0[i_];
+            }
+            state.needmv = true;
+            state.needmtv = false;
+            state.rstate.stage = 2;
+            goto lbl_rcomm;
+        lbl_2:
+            for(i_=0; i_<=m-1;i_++)
+            {
+                state.x[i_] = state.mv[i_];
+            }
+            state.needmv = false;
+            state.needmtv = true;
+            state.rstate.stage = 3;
+            goto lbl_rcomm;
+        lbl_3:
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x1[i_] = state.mtv[i_];
+            }
+            v = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                v = v+math.sqr(state.x1[i]);
+            }
+            state.repnorm = Math.Sqrt(Math.Sqrt(v));
+            if( (double)(v)!=(double)(0) )
+            {
+                v = 1/Math.Sqrt(v);
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    state.x0[i_] = v*state.x1[i_];
+                }
+            }
+            itcnt = itcnt+1;
+            goto lbl_7;
+        lbl_9:
+            result = false;
+            return result;
+            
+            //
+            // Saving state
+            //
+        lbl_rcomm:
+            result = true;
+            state.rstate.ia[0] = n;
+            state.rstate.ia[1] = m;
+            state.rstate.ia[2] = i;
+            state.rstate.ia[3] = itcnt;
+            state.rstate.ra[0] = v;
+            state.rstate.ra[1] = growth;
+            state.rstate.ra[2] = bestgrowth;
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function estimates norm of the sparse M*N matrix A.
+
+        INPUT PARAMETERS:
+            State       -   norm estimator state, must be initialized with a  call
+                            to NormEstimatorCreate()
+            A           -   sparse M*N matrix, must be converted to CRS format
+                            prior to calling this function.
+
+        After this function  is  over  you can call NormEstimatorResults() to get 
+        estimate of the norm(A).
+
+          -- ALGLIB --
+             Copyright 06.12.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void normestimatorestimatesparse(normestimatorstate state,
+            sparse.sparsematrix a)
+        {
+            normestimatorrestart(state);
+            while( normestimatoriteration(state) )
+            {
+                if( state.needmv )
+                {
+                    sparse.sparsemv(a, state.x, ref state.mv);
+                    continue;
+                }
+                if( state.needmtv )
+                {
+                    sparse.sparsemtv(a, state.x, ref state.mtv);
+                    continue;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Matrix norm estimation results
+
+        INPUT PARAMETERS:
+            State   -   algorithm state
+
+        OUTPUT PARAMETERS:
+            Nrm     -   estimate of the matrix norm, Nrm>=0
+
+          -- ALGLIB --
+             Copyright 06.12.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void normestimatorresults(normestimatorstate state,
+            ref double nrm)
+        {
+            nrm = 0;
+
+            nrm = state.repnorm;
+        }
+
+
+        /*************************************************************************
+        This  function  restarts estimator and prepares it for the next estimation
+        round.
+
+        INPUT PARAMETERS:
+            State   -   algorithm state
+          -- ALGLIB --
+             Copyright 06.12.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void normestimatorrestart(normestimatorstate state)
+        {
+            state.rstate.ia = new int[3+1];
+            state.rstate.ra = new double[2+1];
+            state.rstate.stage = -1;
+        }
+
+
+    }
+    public class matdet
+    {
+        /*************************************************************************
+        Determinant calculation of the matrix given by its LU decomposition.
+
+        Input parameters:
+            A       -   LU decomposition of the matrix (output of
+                        RMatrixLU subroutine).
+            Pivots  -   table of permutations which were made during
+                        the LU decomposition.
+                        Output of RMatrixLU subroutine.
+            N       -   (optional) size of matrix A:
+                        * if given, only principal NxN submatrix is processed and
+                          overwritten. other elements are unchanged.
+                        * if not given, automatically determined from matrix size
+                          (A must be square matrix)
+
+        Result: matrix determinant.
+
+          -- ALGLIB --
+             Copyright 2005 by Bochkanov Sergey
+        *************************************************************************/
+        public static double rmatrixludet(double[,] a,
+            int[] pivots,
+            int n)
+        {
+            double result = 0;
+            int i = 0;
+            int s = 0;
+
+            alglib.ap.assert(n>=1, "RMatrixLUDet: N<1!");
+            alglib.ap.assert(alglib.ap.len(pivots)>=n, "RMatrixLUDet: Pivots array is too short!");
+            alglib.ap.assert(alglib.ap.rows(a)>=n, "RMatrixLUDet: rows(A)<N!");
+            alglib.ap.assert(alglib.ap.cols(a)>=n, "RMatrixLUDet: cols(A)<N!");
+            alglib.ap.assert(apserv.apservisfinitematrix(a, n, n), "RMatrixLUDet: A contains infinite or NaN values!");
+            result = 1;
+            s = 1;
+            for(i=0; i<=n-1; i++)
+            {
+                result = result*a[i,i];
+                if( pivots[i]!=i )
+                {
+                    s = -s;
+                }
+            }
+            result = result*s;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Calculation of the determinant of a general matrix
+
+        Input parameters:
+            A       -   matrix, array[0..N-1, 0..N-1]
+            N       -   (optional) size of matrix A:
+                        * if given, only principal NxN submatrix is processed and
+                          overwritten. other elements are unchanged.
+                        * if not given, automatically determined from matrix size
+                          (A must be square matrix)
+
+        Result: determinant of matrix A.
+
+          -- ALGLIB --
+             Copyright 2005 by Bochkanov Sergey
+        *************************************************************************/
+        public static double rmatrixdet(double[,] a,
+            int n)
+        {
+            double result = 0;
+            int[] pivots = new int[0];
+
+            a = (double[,])a.Clone();
+
+            alglib.ap.assert(n>=1, "RMatrixDet: N<1!");
+            alglib.ap.assert(alglib.ap.rows(a)>=n, "RMatrixDet: rows(A)<N!");
+            alglib.ap.assert(alglib.ap.cols(a)>=n, "RMatrixDet: cols(A)<N!");
+            alglib.ap.assert(apserv.apservisfinitematrix(a, n, n), "RMatrixDet: A contains infinite or NaN values!");
+            trfac.rmatrixlu(ref a, n, n, ref pivots);
+            result = rmatrixludet(a, pivots, n);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Determinant calculation of the matrix given by its LU decomposition.
+
+        Input parameters:
+            A       -   LU decomposition of the matrix (output of
+                        RMatrixLU subroutine).
+            Pivots  -   table of permutations which were made during
+                        the LU decomposition.
+                        Output of RMatrixLU subroutine.
+            N       -   (optional) size of matrix A:
+                        * if given, only principal NxN submatrix is processed and
+                          overwritten. other elements are unchanged.
+                        * if not given, automatically determined from matrix size
+                          (A must be square matrix)
+
+        Result: matrix determinant.
+
+          -- ALGLIB --
+             Copyright 2005 by Bochkanov Sergey
+        *************************************************************************/
+        public static complex cmatrixludet(complex[,] a,
+            int[] pivots,
+            int n)
+        {
+            complex result = 0;
+            int i = 0;
+            int s = 0;
+
+            alglib.ap.assert(n>=1, "CMatrixLUDet: N<1!");
+            alglib.ap.assert(alglib.ap.len(pivots)>=n, "CMatrixLUDet: Pivots array is too short!");
+            alglib.ap.assert(alglib.ap.rows(a)>=n, "CMatrixLUDet: rows(A)<N!");
+            alglib.ap.assert(alglib.ap.cols(a)>=n, "CMatrixLUDet: cols(A)<N!");
+            alglib.ap.assert(apserv.apservisfinitecmatrix(a, n, n), "CMatrixLUDet: A contains infinite or NaN values!");
+            result = 1;
+            s = 1;
+            for(i=0; i<=n-1; i++)
+            {
+                result = result*a[i,i];
+                if( pivots[i]!=i )
+                {
+                    s = -s;
+                }
+            }
+            result = result*s;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Calculation of the determinant of a general matrix
+
+        Input parameters:
+            A       -   matrix, array[0..N-1, 0..N-1]
+            N       -   (optional) size of matrix A:
+                        * if given, only principal NxN submatrix is processed and
+                          overwritten. other elements are unchanged.
+                        * if not given, automatically determined from matrix size
+                          (A must be square matrix)
+
+        Result: determinant of matrix A.
+
+          -- ALGLIB --
+             Copyright 2005 by Bochkanov Sergey
+        *************************************************************************/
+        public static complex cmatrixdet(complex[,] a,
+            int n)
+        {
+            complex result = 0;
+            int[] pivots = new int[0];
+
+            a = (complex[,])a.Clone();
+
+            alglib.ap.assert(n>=1, "CMatrixDet: N<1!");
+            alglib.ap.assert(alglib.ap.rows(a)>=n, "CMatrixDet: rows(A)<N!");
+            alglib.ap.assert(alglib.ap.cols(a)>=n, "CMatrixDet: cols(A)<N!");
+            alglib.ap.assert(apserv.apservisfinitecmatrix(a, n, n), "CMatrixDet: A contains infinite or NaN values!");
+            trfac.cmatrixlu(ref a, n, n, ref pivots);
+            result = cmatrixludet(a, pivots, n);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Determinant calculation of the matrix given by the Cholesky decomposition.
+
+        Input parameters:
+            A       -   Cholesky decomposition,
+                        output of SMatrixCholesky subroutine.
+            N       -   (optional) size of matrix A:
+                        * if given, only principal NxN submatrix is processed and
+                          overwritten. other elements are unchanged.
+                        * if not given, automatically determined from matrix size
+                          (A must be square matrix)
+
+        As the determinant is equal to the product of squares of diagonal elements,
+        its not necessary to specify which triangle - lower or upper - the matrix
+        is stored in.
+
+        Result:
+            matrix determinant.
+
+          -- ALGLIB --
+             Copyright 2005-2008 by Bochkanov Sergey
+        *************************************************************************/
+        public static double spdmatrixcholeskydet(double[,] a,
+            int n)
+        {
+            double result = 0;
+            int i = 0;
+            bool f = new bool();
+
+            alglib.ap.assert(n>=1, "SPDMatrixCholeskyDet: N<1!");
+            alglib.ap.assert(alglib.ap.rows(a)>=n, "SPDMatrixCholeskyDet: rows(A)<N!");
+            alglib.ap.assert(alglib.ap.cols(a)>=n, "SPDMatrixCholeskyDet: cols(A)<N!");
+            f = true;
+            for(i=0; i<=n-1; i++)
+            {
+                f = f && math.isfinite(a[i,i]);
+            }
+            alglib.ap.assert(f, "SPDMatrixCholeskyDet: A contains infinite or NaN values!");
+            result = 1;
+            for(i=0; i<=n-1; i++)
+            {
+                result = result*math.sqr(a[i,i]);
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        Determinant calculation of the symmetric positive definite matrix.
+
+        Input parameters:
+            A       -   matrix. Array with elements [0..N-1, 0..N-1].
+            N       -   (optional) size of matrix A:
+                        * if given, only principal NxN submatrix is processed and
+                          overwritten. other elements are unchanged.
+                        * if not given, automatically determined from matrix size
+                          (A must be square matrix)
+            IsUpper -   (optional) storage type:
+                        * if True, symmetric matrix  A  is  given  by  its  upper
+                          triangle, and the lower triangle isnt used/changed  by
+                          function
+                        * if False, symmetric matrix  A  is  given  by  its lower
+                          triangle, and the upper triangle isnt used/changed  by
+                          function
+                        * if not given, both lower and upper  triangles  must  be
+                          filled.
+
+        Result:
+            determinant of matrix A.
+            If matrix A is not positive definite, exception is thrown.
+
+          -- ALGLIB --
+             Copyright 2005-2008 by Bochkanov Sergey
+        *************************************************************************/
+        public static double spdmatrixdet(double[,] a,
+            int n,
+            bool isupper)
+        {
+            double result = 0;
+            bool b = new bool();
+
+            a = (double[,])a.Clone();
+
+            alglib.ap.assert(n>=1, "SPDMatrixDet: N<1!");
+            alglib.ap.assert(alglib.ap.rows(a)>=n, "SPDMatrixDet: rows(A)<N!");
+            alglib.ap.assert(alglib.ap.cols(a)>=n, "SPDMatrixDet: cols(A)<N!");
+            alglib.ap.assert(apserv.isfinitertrmatrix(a, n, isupper), "SPDMatrixDet: A contains infinite or NaN values!");
+            b = trfac.spdmatrixcholesky(ref a, n, isupper);
+            alglib.ap.assert(b, "SPDMatrixDet: A is not SPD!");
+            result = spdmatrixcholeskydet(a, n);
+            return result;
+        }
+
+
+    }
+    public class spdgevd
+    {
+        /*************************************************************************
+        Algorithm for solving the following generalized symmetric positive-definite
+        eigenproblem:
+            A*x = lambda*B*x (1) or
+            A*B*x = lambda*x (2) or
+            B*A*x = lambda*x (3).
+        where A is a symmetric matrix, B - symmetric positive-definite matrix.
+        The problem is solved by reducing it to an ordinary  symmetric  eigenvalue
+        problem.
+
+        Input parameters:
+            A           -   symmetric matrix which is given by its upper or lower
+                            triangular part.
+                            Array whose indexes range within [0..N-1, 0..N-1].
+            N           -   size of matrices A and B.
+            IsUpperA    -   storage format of matrix A.
+            B           -   symmetric positive-definite matrix which is given by
+                            its upper or lower triangular part.
+                            Array whose indexes range within [0..N-1, 0..N-1].
+            IsUpperB    -   storage format of matrix B.
+            ZNeeded     -   if ZNeeded is equal to:
+                             * 0, the eigenvectors are not returned;
+                             * 1, the eigenvectors are returned.
+            ProblemType -   if ProblemType is equal to:
+                             * 1, the following problem is solved: A*x = lambda*B*x;
+                             * 2, the following problem is solved: A*B*x = lambda*x;
+                             * 3, the following problem is solved: B*A*x = lambda*x.
+
+        Output parameters:
+            D           -   eigenvalues in ascending order.
+                            Array whose index ranges within [0..N-1].
+            Z           -   if ZNeeded is equal to:
+                             * 0, Z hasnt changed;
+                             * 1, Z contains eigenvectors.
+                            Array whose indexes range within [0..N-1, 0..N-1].
+                            The eigenvectors are stored in matrix columns. It should
+                            be noted that the eigenvectors in such problems do not
+                            form an orthogonal system.
+
+        Result:
+            True, if the problem was solved successfully.
+            False, if the error occurred during the Cholesky decomposition of matrix
+            B (the matrix isnt positive-definite) or during the work of the iterative
+            algorithm for solving the symmetric eigenproblem.
+
+        See also the GeneralizedSymmetricDefiniteEVDReduce subroutine.
+
+          -- ALGLIB --
+             Copyright 1.28.2006 by Bochkanov Sergey
+        *************************************************************************/
+        public static bool smatrixgevd(double[,] a,
+            int n,
+            bool isuppera,
+            double[,] b,
+            bool isupperb,
+            int zneeded,
+            int problemtype,
+            ref double[] d,
+            ref double[,] z)
+        {
+            bool result = new bool();
+            double[,] r = new double[0,0];
+            double[,] t = new double[0,0];
+            bool isupperr = new bool();
+            int j1 = 0;
+            int j2 = 0;
+            int j1inc = 0;
+            int j2inc = 0;
+            int i = 0;
+            int j = 0;
+            double v = 0;
+            int i_ = 0;
+
+            a = (double[,])a.Clone();
+            d = new double[0];
+            z = new double[0,0];
+
+            
+            //
+            // Reduce and solve
+            //
+            result = smatrixgevdreduce(ref a, n, isuppera, b, isupperb, problemtype, ref r, ref isupperr);
+            if( !result )
+            {
+                return result;
+            }
+            result = evd.smatrixevd(a, n, zneeded, isuppera, ref d, ref t);
+            if( !result )
+            {
+                return result;
+            }
+            
+            //
+            // Transform eigenvectors if needed
+            //
+            if( zneeded!=0 )
+            {
+                
+                //
+                // fill Z with zeros
+                //
+                z = new double[n-1+1, n-1+1];
+                for(j=0; j<=n-1; j++)
+                {
+                    z[0,j] = 0.0;
+                }
+                for(i=1; i<=n-1; i++)
+                {
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        z[i,i_] = z[0,i_];
+                    }
+                }
+                
+                //
+                // Setup R properties
+                //
+                if( isupperr )
+                {
+                    j1 = 0;
+                    j2 = n-1;
+                    j1inc = 1;
+                    j2inc = 0;
+                }
+                else
+                {
+                    j1 = 0;
+                    j2 = 0;
+                    j1inc = 0;
+                    j2inc = 1;
+                }
+                
+                //
+                // Calculate R*Z
+                //
+                for(i=0; i<=n-1; i++)
+                {
+                    for(j=j1; j<=j2; j++)
+                    {
+                        v = r[i,j];
+                        for(i_=0; i_<=n-1;i_++)
+                        {
+                            z[i,i_] = z[i,i_] + v*t[j,i_];
+                        }
+                    }
+                    j1 = j1+j1inc;
+                    j2 = j2+j2inc;
+                }
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        Algorithm for reduction of the following generalized symmetric positive-
+        definite eigenvalue problem:
+            A*x = lambda*B*x (1) or
+            A*B*x = lambda*x (2) or
+            B*A*x = lambda*x (3)
+        to the symmetric eigenvalues problem C*y = lambda*y (eigenvalues of this and
+        the given problems are the same, and the eigenvectors of the given problem
+        could be obtained by multiplying the obtained eigenvectors by the
+        transformation matrix x = R*y).
+
+        Here A is a symmetric matrix, B - symmetric positive-definite matrix.
+
+        Input parameters:
+            A           -   symmetric matrix which is given by its upper or lower
+                            triangular part.
+                            Array whose indexes range within [0..N-1, 0..N-1].
+            N           -   size of matrices A and B.
+            IsUpperA    -   storage format of matrix A.
+            B           -   symmetric positive-definite matrix which is given by
+                            its upper or lower triangular part.
+                            Array whose indexes range within [0..N-1, 0..N-1].
+            IsUpperB    -   storage format of matrix B.
+            ProblemType -   if ProblemType is equal to:
+                             * 1, the following problem is solved: A*x = lambda*B*x;
+                             * 2, the following problem is solved: A*B*x = lambda*x;
+                             * 3, the following problem is solved: B*A*x = lambda*x.
+
+        Output parameters:
+            A           -   symmetric matrix which is given by its upper or lower
+                            triangle depending on IsUpperA. Contains matrix C.
+                            Array whose indexes range within [0..N-1, 0..N-1].
+            R           -   upper triangular or low triangular transformation matrix
+                            which is used to obtain the eigenvectors of a given problem
+                            as the product of eigenvectors of C (from the right) and
+                            matrix R (from the left). If the matrix is upper
+                            triangular, the elements below the main diagonal
+                            are equal to 0 (and vice versa). Thus, we can perform
+                            the multiplication without taking into account the
+                            internal structure (which is an easier though less
+                            effective way).
+                            Array whose indexes range within [0..N-1, 0..N-1].
+            IsUpperR    -   type of matrix R (upper or lower triangular).
+
+        Result:
+            True, if the problem was reduced successfully.
+            False, if the error occurred during the Cholesky decomposition of
+                matrix B (the matrix is not positive-definite).
+
+          -- ALGLIB --
+             Copyright 1.28.2006 by Bochkanov Sergey
+        *************************************************************************/
+        public static bool smatrixgevdreduce(ref double[,] a,
+            int n,
+            bool isuppera,
+            double[,] b,
+            bool isupperb,
+            int problemtype,
+            ref double[,] r,
+            ref bool isupperr)
+        {
+            bool result = new bool();
+            double[,] t = new double[0,0];
+            double[] w1 = new double[0];
+            double[] w2 = new double[0];
+            double[] w3 = new double[0];
+            int i = 0;
+            int j = 0;
+            double v = 0;
+            matinv.matinvreport rep = new matinv.matinvreport();
+            int info = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            r = new double[0,0];
+            isupperr = new bool();
+
+            alglib.ap.assert(n>0, "SMatrixGEVDReduce: N<=0!");
+            alglib.ap.assert((problemtype==1 || problemtype==2) || problemtype==3, "SMatrixGEVDReduce: incorrect ProblemType!");
+            result = true;
+            
+            //
+            // Problem 1:  A*x = lambda*B*x
+            //
+            // Reducing to:
+            //     C*y = lambda*y
+            //     C = L^(-1) * A * L^(-T)
+            //     x = L^(-T) * y
+            //
+            if( problemtype==1 )
+            {
+                
+                //
+                // Factorize B in T: B = LL'
+                //
+                t = new double[n-1+1, n-1+1];
+                if( isupperb )
+                {
+                    for(i=0; i<=n-1; i++)
+                    {
+                        for(i_=i; i_<=n-1;i_++)
+                        {
+                            t[i_,i] = b[i,i_];
+                        }
+                    }
+                }
+                else
+                {
+                    for(i=0; i<=n-1; i++)
+                    {
+                        for(i_=0; i_<=i;i_++)
+                        {
+                            t[i,i_] = b[i,i_];
+                        }
+                    }
+                }
+                if( !trfac.spdmatrixcholesky(ref t, n, false) )
+                {
+                    result = false;
+                    return result;
+                }
+                
+                //
+                // Invert L in T
+                //
+                matinv.rmatrixtrinverse(ref t, n, false, false, ref info, rep);
+                if( info<=0 )
+                {
+                    result = false;
+                    return result;
+                }
+                
+                //
+                // Build L^(-1) * A * L^(-T) in R
+                //
+                w1 = new double[n+1];
+                w2 = new double[n+1];
+                r = new double[n-1+1, n-1+1];
+                for(j=1; j<=n; j++)
+                {
+                    
+                    //
+                    // Form w2 = A * l'(j) (here l'(j) is j-th column of L^(-T))
+                    //
+                    i1_ = (0) - (1);
+                    for(i_=1; i_<=j;i_++)
+                    {
+                        w1[i_] = t[j-1,i_+i1_];
+                    }
+                    sblas.symmetricmatrixvectormultiply(a, isuppera, 0, j-1, w1, 1.0, ref w2);
+                    if( isuppera )
+                    {
+                        blas.matrixvectormultiply(a, 0, j-1, j, n-1, true, w1, 1, j, 1.0, ref w2, j+1, n, 0.0);
+                    }
+                    else
+                    {
+                        blas.matrixvectormultiply(a, j, n-1, 0, j-1, false, w1, 1, j, 1.0, ref w2, j+1, n, 0.0);
+                    }
+                    
+                    //
+                    // Form l(i)*w2 (here l(i) is i-th row of L^(-1))
+                    //
+                    for(i=1; i<=n; i++)
+                    {
+                        i1_ = (1)-(0);
+                        v = 0.0;
+                        for(i_=0; i_<=i-1;i_++)
+                        {
+                            v += t[i-1,i_]*w2[i_+i1_];
+                        }
+                        r[i-1,j-1] = v;
+                    }
+                }
+                
+                //
+                // Copy R to A
+                //
+                for(i=0; i<=n-1; i++)
+                {
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        a[i,i_] = r[i,i_];
+                    }
+                }
+                
+                //
+                // Copy L^(-1) from T to R and transpose
+                //
+                isupperr = true;
+                for(i=0; i<=n-1; i++)
+                {
+                    for(j=0; j<=i-1; j++)
+                    {
+                        r[i,j] = 0;
+                    }
+                }
+                for(i=0; i<=n-1; i++)
+                {
+                    for(i_=i; i_<=n-1;i_++)
+                    {
+                        r[i,i_] = t[i_,i];
+                    }
+                }
+                return result;
+            }
+            
+            //
+            // Problem 2:  A*B*x = lambda*x
+            // or
+            // problem 3:  B*A*x = lambda*x
+            //
+            // Reducing to:
+            //     C*y = lambda*y
+            //     C = U * A * U'
+            //     B = U'* U
+            //
+            if( problemtype==2 || problemtype==3 )
+            {
+                
+                //
+                // Factorize B in T: B = U'*U
+                //
+                t = new double[n-1+1, n-1+1];
+                if( isupperb )
+                {
+                    for(i=0; i<=n-1; i++)
+                    {
+                        for(i_=i; i_<=n-1;i_++)
+                        {
+                            t[i,i_] = b[i,i_];
+                        }
+                    }
+                }
+                else
+                {
+                    for(i=0; i<=n-1; i++)
+                    {
+                        for(i_=i; i_<=n-1;i_++)
+                        {
+                            t[i,i_] = b[i_,i];
+                        }
+                    }
+                }
+                if( !trfac.spdmatrixcholesky(ref t, n, true) )
+                {
+                    result = false;
+                    return result;
+                }
+                
+                //
+                // Build U * A * U' in R
+                //
+                w1 = new double[n+1];
+                w2 = new double[n+1];
+                w3 = new double[n+1];
+                r = new double[n-1+1, n-1+1];
+                for(j=1; j<=n; j++)
+                {
+                    
+                    //
+                    // Form w2 = A * u'(j) (here u'(j) is j-th column of U')
+                    //
+                    i1_ = (j-1) - (1);
+                    for(i_=1; i_<=n-j+1;i_++)
+                    {
+                        w1[i_] = t[j-1,i_+i1_];
+                    }
+                    sblas.symmetricmatrixvectormultiply(a, isuppera, j-1, n-1, w1, 1.0, ref w3);
+                    i1_ = (1) - (j);
+                    for(i_=j; i_<=n;i_++)
+                    {
+                        w2[i_] = w3[i_+i1_];
+                    }
+                    i1_ = (j-1) - (j);
+                    for(i_=j; i_<=n;i_++)
+                    {
+                        w1[i_] = t[j-1,i_+i1_];
+                    }
+                    if( isuppera )
+                    {
+                        blas.matrixvectormultiply(a, 0, j-2, j-1, n-1, false, w1, j, n, 1.0, ref w2, 1, j-1, 0.0);
+                    }
+                    else
+                    {
+                        blas.matrixvectormultiply(a, j-1, n-1, 0, j-2, true, w1, j, n, 1.0, ref w2, 1, j-1, 0.0);
+                    }
+                    
+                    //
+                    // Form u(i)*w2 (here u(i) is i-th row of U)
+                    //
+                    for(i=1; i<=n; i++)
+                    {
+                        i1_ = (i)-(i-1);
+                        v = 0.0;
+                        for(i_=i-1; i_<=n-1;i_++)
+                        {
+                            v += t[i-1,i_]*w2[i_+i1_];
+                        }
+                        r[i-1,j-1] = v;
+                    }
+                }
+                
+                //
+                // Copy R to A
+                //
+                for(i=0; i<=n-1; i++)
+                {
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        a[i,i_] = r[i,i_];
+                    }
+                }
+                if( problemtype==2 )
+                {
+                    
+                    //
+                    // Invert U in T
+                    //
+                    matinv.rmatrixtrinverse(ref t, n, true, false, ref info, rep);
+                    if( info<=0 )
+                    {
+                        result = false;
+                        return result;
+                    }
+                    
+                    //
+                    // Copy U^-1 from T to R
+                    //
+                    isupperr = true;
+                    for(i=0; i<=n-1; i++)
+                    {
+                        for(j=0; j<=i-1; j++)
+                        {
+                            r[i,j] = 0;
+                        }
+                    }
+                    for(i=0; i<=n-1; i++)
+                    {
+                        for(i_=i; i_<=n-1;i_++)
+                        {
+                            r[i,i_] = t[i,i_];
+                        }
+                    }
+                }
+                else
+                {
+                    
+                    //
+                    // Copy U from T to R and transpose
+                    //
+                    isupperr = false;
+                    for(i=0; i<=n-1; i++)
+                    {
+                        for(j=i+1; j<=n-1; j++)
+                        {
+                            r[i,j] = 0;
+                        }
+                    }
+                    for(i=0; i<=n-1; i++)
+                    {
+                        for(i_=i; i_<=n-1;i_++)
+                        {
+                            r[i_,i] = t[i,i_];
+                        }
+                    }
+                }
+            }
+            return result;
+        }
+
+
+    }
+    public class inverseupdate
+    {
+        /*************************************************************************
+        Inverse matrix update by the Sherman-Morrison formula
+
+        The algorithm updates matrix A^-1 when adding a number to an element
+        of matrix A.
+
+        Input parameters:
+            InvA    -   inverse of matrix A.
+                        Array whose indexes range within [0..N-1, 0..N-1].
+            N       -   size of matrix A.
+            UpdRow  -   row where the element to be updated is stored.
+            UpdColumn - column where the element to be updated is stored.
+            UpdVal  -   a number to be added to the element.
+
+
+        Output parameters:
+            InvA    -   inverse of modified matrix A.
+
+          -- ALGLIB --
+             Copyright 2005 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rmatrixinvupdatesimple(ref double[,] inva,
+            int n,
+            int updrow,
+            int updcolumn,
+            double updval)
+        {
+            double[] t1 = new double[0];
+            double[] t2 = new double[0];
+            int i = 0;
+            double lambdav = 0;
+            double vt = 0;
+            int i_ = 0;
+
+            alglib.ap.assert(updrow>=0 && updrow<n, "RMatrixInvUpdateSimple: incorrect UpdRow!");
+            alglib.ap.assert(updcolumn>=0 && updcolumn<n, "RMatrixInvUpdateSimple: incorrect UpdColumn!");
+            t1 = new double[n-1+1];
+            t2 = new double[n-1+1];
+            
+            //
+            // T1 = InvA * U
+            //
+            for(i_=0; i_<=n-1;i_++)
+            {
+                t1[i_] = inva[i_,updrow];
+            }
+            
+            //
+            // T2 = v*InvA
+            //
+            for(i_=0; i_<=n-1;i_++)
+            {
+                t2[i_] = inva[updcolumn,i_];
+            }
+            
+            //
+            // Lambda = v * InvA * U
+            //
+            lambdav = updval*inva[updcolumn,updrow];
+            
+            //
+            // InvA = InvA - correction
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                vt = updval*t1[i];
+                vt = vt/(1+lambdav);
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    inva[i,i_] = inva[i,i_] - vt*t2[i_];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Inverse matrix update by the Sherman-Morrison formula
+
+        The algorithm updates matrix A^-1 when adding a vector to a row
+        of matrix A.
+
+        Input parameters:
+            InvA    -   inverse of matrix A.
+                        Array whose indexes range within [0..N-1, 0..N-1].
+            N       -   size of matrix A.
+            UpdRow  -   the row of A whose vector V was added.
+                        0 <= Row <= N-1
+            V       -   the vector to be added to a row.
+                        Array whose index ranges within [0..N-1].
+
+        Output parameters:
+            InvA    -   inverse of modified matrix A.
+
+          -- ALGLIB --
+             Copyright 2005 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rmatrixinvupdaterow(ref double[,] inva,
+            int n,
+            int updrow,
+            double[] v)
+        {
+            double[] t1 = new double[0];
+            double[] t2 = new double[0];
+            int i = 0;
+            int j = 0;
+            double lambdav = 0;
+            double vt = 0;
+            int i_ = 0;
+
+            t1 = new double[n-1+1];
+            t2 = new double[n-1+1];
+            
+            //
+            // T1 = InvA * U
+            //
+            for(i_=0; i_<=n-1;i_++)
+            {
+                t1[i_] = inva[i_,updrow];
+            }
+            
+            //
+            // T2 = v*InvA
+            // Lambda = v * InvA * U
+            //
+            for(j=0; j<=n-1; j++)
+            {
+                vt = 0.0;
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    vt += v[i_]*inva[i_,j];
+                }
+                t2[j] = vt;
+            }
+            lambdav = t2[updrow];
+            
+            //
+            // InvA = InvA - correction
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                vt = t1[i]/(1+lambdav);
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    inva[i,i_] = inva[i,i_] - vt*t2[i_];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Inverse matrix update by the Sherman-Morrison formula
+
+        The algorithm updates matrix A^-1 when adding a vector to a column
+        of matrix A.
+
+        Input parameters:
+            InvA        -   inverse of matrix A.
+                            Array whose indexes range within [0..N-1, 0..N-1].
+            N           -   size of matrix A.
+            UpdColumn   -   the column of A whose vector U was added.
+                            0 <= UpdColumn <= N-1
+            U           -   the vector to be added to a column.
+                            Array whose index ranges within [0..N-1].
+
+        Output parameters:
+            InvA        -   inverse of modified matrix A.
+
+          -- ALGLIB --
+             Copyright 2005 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rmatrixinvupdatecolumn(ref double[,] inva,
+            int n,
+            int updcolumn,
+            double[] u)
+        {
+            double[] t1 = new double[0];
+            double[] t2 = new double[0];
+            int i = 0;
+            double lambdav = 0;
+            double vt = 0;
+            int i_ = 0;
+
+            t1 = new double[n-1+1];
+            t2 = new double[n-1+1];
+            
+            //
+            // T1 = InvA * U
+            // Lambda = v * InvA * U
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                vt = 0.0;
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    vt += inva[i,i_]*u[i_];
+                }
+                t1[i] = vt;
+            }
+            lambdav = t1[updcolumn];
+            
+            //
+            // T2 = v*InvA
+            //
+            for(i_=0; i_<=n-1;i_++)
+            {
+                t2[i_] = inva[updcolumn,i_];
+            }
+            
+            //
+            // InvA = InvA - correction
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                vt = t1[i]/(1+lambdav);
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    inva[i,i_] = inva[i,i_] - vt*t2[i_];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Inverse matrix update by the Sherman-Morrison formula
+
+        The algorithm computes the inverse of matrix A+u*v by using the given matrix
+        A^-1 and the vectors u and v.
+
+        Input parameters:
+            InvA    -   inverse of matrix A.
+                        Array whose indexes range within [0..N-1, 0..N-1].
+            N       -   size of matrix A.
+            U       -   the vector modifying the matrix.
+                        Array whose index ranges within [0..N-1].
+            V       -   the vector modifying the matrix.
+                        Array whose index ranges within [0..N-1].
+
+        Output parameters:
+            InvA - inverse of matrix A + u*v'.
+
+          -- ALGLIB --
+             Copyright 2005 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rmatrixinvupdateuv(ref double[,] inva,
+            int n,
+            double[] u,
+            double[] v)
+        {
+            double[] t1 = new double[0];
+            double[] t2 = new double[0];
+            int i = 0;
+            int j = 0;
+            double lambdav = 0;
+            double vt = 0;
+            int i_ = 0;
+
+            t1 = new double[n-1+1];
+            t2 = new double[n-1+1];
+            
+            //
+            // T1 = InvA * U
+            // Lambda = v * T1
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                vt = 0.0;
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    vt += inva[i,i_]*u[i_];
+                }
+                t1[i] = vt;
+            }
+            lambdav = 0.0;
+            for(i_=0; i_<=n-1;i_++)
+            {
+                lambdav += v[i_]*t1[i_];
+            }
+            
+            //
+            // T2 = v*InvA
+            //
+            for(j=0; j<=n-1; j++)
+            {
+                vt = 0.0;
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    vt += v[i_]*inva[i_,j];
+                }
+                t2[j] = vt;
+            }
+            
+            //
+            // InvA = InvA - correction
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                vt = t1[i]/(1+lambdav);
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    inva[i,i_] = inva[i,i_] - vt*t2[i_];
+                }
+            }
+        }
+
+
+    }
+    public class schur
+    {
+        /*************************************************************************
+        Subroutine performing the Schur decomposition of a general matrix by using
+        the QR algorithm with multiple shifts.
+
+        The source matrix A is represented as S'*A*S = T, where S is an orthogonal
+        matrix (Schur vectors), T - upper quasi-triangular matrix (with blocks of
+        sizes 1x1 and 2x2 on the main diagonal).
+
+        Input parameters:
+            A   -   matrix to be decomposed.
+                    Array whose indexes range within [0..N-1, 0..N-1].
+            N   -   size of A, N>=0.
+
+
+        Output parameters:
+            A   -   contains matrix T.
+                    Array whose indexes range within [0..N-1, 0..N-1].
+            S   -   contains Schur vectors.
+                    Array whose indexes range within [0..N-1, 0..N-1].
+
+        Note 1:
+            The block structure of matrix T can be easily recognized: since all
+            the elements below the blocks are zeros, the elements a[i+1,i] which
+            are equal to 0 show the block border.
+
+        Note 2:
+            The algorithm performance depends on the value of the internal parameter
+            NS of the InternalSchurDecomposition subroutine which defines the number
+            of shifts in the QR algorithm (similarly to the block width in block-matrix
+            algorithms in linear algebra). If you require maximum performance on
+            your machine, it is recommended to adjust this parameter manually.
+
+        Result:
+            True,
+                if the algorithm has converged and parameters A and S contain the result.
+            False,
+                if the algorithm has not converged.
+
+        Algorithm implemented on the basis of the DHSEQR subroutine (LAPACK 3.0 library).
+        *************************************************************************/
+        public static bool rmatrixschur(ref double[,] a,
+            int n,
+            ref double[,] s)
+        {
+            bool result = new bool();
+            double[] tau = new double[0];
+            double[] wi = new double[0];
+            double[] wr = new double[0];
+            double[,] a1 = new double[0,0];
+            double[,] s1 = new double[0,0];
+            int info = 0;
+            int i = 0;
+            int j = 0;
+
+            s = new double[0,0];
+
+            
+            //
+            // Upper Hessenberg form of the 0-based matrix
+            //
+            ortfac.rmatrixhessenberg(ref a, n, ref tau);
+            ortfac.rmatrixhessenbergunpackq(a, n, tau, ref s);
+            
+            //
+            // Convert from 0-based arrays to 1-based,
+            // then call InternalSchurDecomposition
+            // Awkward, of course, but Schur decompisiton subroutine
+            // is too complex to fix it.
+            //
+            //
+            a1 = new double[n+1, n+1];
+            s1 = new double[n+1, n+1];
+            for(i=1; i<=n; i++)
+            {
+                for(j=1; j<=n; j++)
+                {
+                    a1[i,j] = a[i-1,j-1];
+                    s1[i,j] = s[i-1,j-1];
+                }
+            }
+            hsschur.internalschurdecomposition(ref a1, n, 1, 1, ref wr, ref wi, ref s1, ref info);
+            result = info==0;
+            
+            //
+            // convert from 1-based arrays to -based
+            //
+            for(i=1; i<=n; i++)
+            {
+                for(j=1; j<=n; j++)
+                {
+                    a[i-1,j-1] = a1[i,j];
+                    s[i-1,j-1] = s1[i,j];
+                }
+            }
+            return result;
+        }
+
+
+    }
+}
+
Index: /trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.5.0/ALGLIB-3.5.0/optimization.cs
===================================================================
--- /trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.5.0/ALGLIB-3.5.0/optimization.cs	(revision 7673)
+++ /trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.5.0/ALGLIB-3.5.0/optimization.cs	(revision 7673)
@@ -0,0 +1,15752 @@
+/*************************************************************************
+Copyright (c) Sergey Bochkanov (ALGLIB project).
+
+>>> SOURCE LICENSE >>>
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation (www.fsf.org); either version 2 of the
+License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+A copy of the GNU General Public License is available at
+http://www.fsf.org/licensing/licenses
+>>> END OF LICENSE >>>
+*************************************************************************/
+#pragma warning disable 162
+#pragma warning disable 219
+using System;
+
+public partial class alglib
+{
+
+
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    This object stores state of the nonlinear CG optimizer.
+
+    You should use ALGLIB functions to work with this object.
+    *************************************************************************/
+    public class mincgstate
+    {
+        //
+        // Public declarations
+        //
+        public bool needf { get { return _innerobj.needf; } set { _innerobj.needf = value; } }
+        public bool needfg { get { return _innerobj.needfg; } set { _innerobj.needfg = value; } }
+        public bool xupdated { get { return _innerobj.xupdated; } set { _innerobj.xupdated = value; } }
+        public double f { get { return _innerobj.f; } set { _innerobj.f = value; } }
+        public double[] g { get { return _innerobj.g; } }
+        public double[] x { get { return _innerobj.x; } }
+
+        public mincgstate()
+        {
+            _innerobj = new mincg.mincgstate();
+        }
+
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private mincg.mincgstate _innerobj;
+        public mincg.mincgstate innerobj { get { return _innerobj; } }
+        public mincgstate(mincg.mincgstate obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+
+    /*************************************************************************
+
+    *************************************************************************/
+    public class mincgreport
+    {
+        //
+        // Public declarations
+        //
+        public int iterationscount { get { return _innerobj.iterationscount; } set { _innerobj.iterationscount = value; } }
+        public int nfev { get { return _innerobj.nfev; } set { _innerobj.nfev = value; } }
+        public int terminationtype { get { return _innerobj.terminationtype; } set { _innerobj.terminationtype = value; } }
+
+        public mincgreport()
+        {
+            _innerobj = new mincg.mincgreport();
+        }
+
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private mincg.mincgreport _innerobj;
+        public mincg.mincgreport innerobj { get { return _innerobj; } }
+        public mincgreport(mincg.mincgreport obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+    /*************************************************************************
+            NONLINEAR CONJUGATE GRADIENT METHOD
+
+    DESCRIPTION:
+    The subroutine minimizes function F(x) of N arguments by using one of  the
+    nonlinear conjugate gradient methods.
+
+    These CG methods are globally convergent (even on non-convex functions) as
+    long as grad(f) is Lipschitz continuous in  a  some  neighborhood  of  the
+    L = { x : f(x)<=f(x0) }.
+
+
+    REQUIREMENTS:
+    Algorithm will request following information during its operation:
+    * function value F and its gradient G (simultaneously) at given point X
+
+
+    USAGE:
+    1. User initializes algorithm state with MinCGCreate() call
+    2. User tunes solver parameters with MinCGSetCond(), MinCGSetStpMax() and
+       other functions
+    3. User calls MinCGOptimize() function which takes algorithm  state   and
+       pointer (delegate, etc.) to callback function which calculates F/G.
+    4. User calls MinCGResults() to get solution
+    5. Optionally, user may call MinCGRestartFrom() to solve another  problem
+       with same N but another starting point and/or another function.
+       MinCGRestartFrom() allows to reuse already initialized structure.
+
+
+    INPUT PARAMETERS:
+        N       -   problem dimension, N>0:
+                    * if given, only leading N elements of X are used
+                    * if not given, automatically determined from size of X
+        X       -   starting point, array[0..N-1].
+
+    OUTPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+
+      -- ALGLIB --
+         Copyright 25.03.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mincgcreate(int n, double[] x, out mincgstate state)
+    {
+        state = new mincgstate();
+        mincg.mincgcreate(n, x, state.innerobj);
+        return;
+    }
+    public static void mincgcreate(double[] x, out mincgstate state)
+    {
+        int n;
+
+        state = new mincgstate();
+        n = ap.len(x);
+        mincg.mincgcreate(n, x, state.innerobj);
+
+        return;
+    }
+
+    /*************************************************************************
+    The subroutine is finite difference variant of MinCGCreate(). It uses
+    finite differences in order to differentiate target function.
+
+    Description below contains information which is specific to this function
+    only. We recommend to read comments on MinCGCreate() in order to get more
+    information about creation of CG optimizer.
+
+    INPUT PARAMETERS:
+        N       -   problem dimension, N>0:
+                    * if given, only leading N elements of X are used
+                    * if not given, automatically determined from size of X
+        X       -   starting point, array[0..N-1].
+        DiffStep-   differentiation step, >0
+
+    OUTPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+
+    NOTES:
+    1. algorithm uses 4-point central formula for differentiation.
+    2. differentiation step along I-th axis is equal to DiffStep*S[I] where
+       S[] is scaling vector which can be set by MinCGSetScale() call.
+    3. we recommend you to use moderate values of  differentiation  step.  Too
+       large step will result in too large truncation  errors, while too small
+       step will result in too large numerical  errors.  1.0E-6  can  be  good
+       value to start with.
+    4. Numerical  differentiation  is   very   inefficient  -   one   gradient
+       calculation needs 4*N function evaluations. This function will work for
+       any N - either small (1...10), moderate (10...100) or  large  (100...).
+       However, performance penalty will be too severe for any N's except  for
+       small ones.
+       We should also say that code which relies on numerical  differentiation
+       is  less  robust  and  precise.  L-BFGS  needs  exact  gradient values.
+       Imprecise  gradient may slow down  convergence,  especially  on  highly
+       nonlinear problems.
+       Thus  we  recommend to use this function for fast prototyping on small-
+       dimensional problems only, and to implement analytical gradient as soon
+       as possible.
+
+      -- ALGLIB --
+         Copyright 16.05.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mincgcreatef(int n, double[] x, double diffstep, out mincgstate state)
+    {
+        state = new mincgstate();
+        mincg.mincgcreatef(n, x, diffstep, state.innerobj);
+        return;
+    }
+    public static void mincgcreatef(double[] x, double diffstep, out mincgstate state)
+    {
+        int n;
+
+        state = new mincgstate();
+        n = ap.len(x);
+        mincg.mincgcreatef(n, x, diffstep, state.innerobj);
+
+        return;
+    }
+
+    /*************************************************************************
+    This function sets stopping conditions for CG optimization algorithm.
+
+    INPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+        EpsG    -   >=0
+                    The  subroutine  finishes  its  work   if   the  condition
+                    |v|<EpsG is satisfied, where:
+                    * |.| means Euclidian norm
+                    * v - scaled gradient vector, v[i]=g[i]*s[i]
+                    * g - gradient
+                    * s - scaling coefficients set by MinCGSetScale()
+        EpsF    -   >=0
+                    The  subroutine  finishes  its work if on k+1-th iteration
+                    the  condition  |F(k+1)-F(k)|<=EpsF*max{|F(k)|,|F(k+1)|,1}
+                    is satisfied.
+        EpsX    -   >=0
+                    The subroutine finishes its work if  on  k+1-th  iteration
+                    the condition |v|<=EpsX is fulfilled, where:
+                    * |.| means Euclidian norm
+                    * v - scaled step vector, v[i]=dx[i]/s[i]
+                    * dx - ste pvector, dx=X(k+1)-X(k)
+                    * s - scaling coefficients set by MinCGSetScale()
+        MaxIts  -   maximum number of iterations. If MaxIts=0, the  number  of
+                    iterations is unlimited.
+
+    Passing EpsG=0, EpsF=0, EpsX=0 and MaxIts=0 (simultaneously) will lead to
+    automatic stopping criterion selection (small EpsX).
+
+      -- ALGLIB --
+         Copyright 02.04.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mincgsetcond(mincgstate state, double epsg, double epsf, double epsx, int maxits)
+    {
+
+        mincg.mincgsetcond(state.innerobj, epsg, epsf, epsx, maxits);
+        return;
+    }
+
+    /*************************************************************************
+    This function sets scaling coefficients for CG optimizer.
+
+    ALGLIB optimizers use scaling matrices to test stopping  conditions  (step
+    size and gradient are scaled before comparison with tolerances).  Scale of
+    the I-th variable is a translation invariant measure of:
+    a) "how large" the variable is
+    b) how large the step should be to make significant changes in the function
+
+    Scaling is also used by finite difference variant of CG optimizer  -  step
+    along I-th axis is equal to DiffStep*S[I].
+
+    In   most   optimizers  (and  in  the  CG  too)  scaling is NOT a form  of
+    preconditioning. It just  affects  stopping  conditions.  You  should  set
+    preconditioner by separate call to one of the MinCGSetPrec...() functions.
+
+    There  is  special  preconditioning  mode, however,  which  uses   scaling
+    coefficients to form diagonal preconditioning matrix. You  can  turn  this
+    mode on, if you want.   But  you should understand that scaling is not the
+    same thing as preconditioning - these are two different, although  related
+    forms of tuning solver.
+
+    INPUT PARAMETERS:
+        State   -   structure stores algorithm state
+        S       -   array[N], non-zero scaling coefficients
+                    S[i] may be negative, sign doesn't matter.
+
+      -- ALGLIB --
+         Copyright 14.01.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mincgsetscale(mincgstate state, double[] s)
+    {
+
+        mincg.mincgsetscale(state.innerobj, s);
+        return;
+    }
+
+    /*************************************************************************
+    This function turns on/off reporting.
+
+    INPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+        NeedXRep-   whether iteration reports are needed or not
+
+    If NeedXRep is True, algorithm will call rep() callback function if  it is
+    provided to MinCGOptimize().
+
+      -- ALGLIB --
+         Copyright 02.04.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mincgsetxrep(mincgstate state, bool needxrep)
+    {
+
+        mincg.mincgsetxrep(state.innerobj, needxrep);
+        return;
+    }
+
+    /*************************************************************************
+    This function sets CG algorithm.
+
+    INPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+        CGType  -   algorithm type:
+                    * -1    automatic selection of the best algorithm
+                    * 0     DY (Dai and Yuan) algorithm
+                    * 1     Hybrid DY-HS algorithm
+
+      -- ALGLIB --
+         Copyright 02.04.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mincgsetcgtype(mincgstate state, int cgtype)
+    {
+
+        mincg.mincgsetcgtype(state.innerobj, cgtype);
+        return;
+    }
+
+    /*************************************************************************
+    This function sets maximum step length
+
+    INPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+        StpMax  -   maximum step length, >=0. Set StpMax to 0.0,  if you don't
+                    want to limit step length.
+
+    Use this subroutine when you optimize target function which contains exp()
+    or  other  fast  growing  functions,  and optimization algorithm makes too
+    large  steps  which  leads  to overflow. This function allows us to reject
+    steps  that  are  too  large  (and  therefore  expose  us  to the possible
+    overflow) without actually calculating function value at the x+stp*d.
+
+      -- ALGLIB --
+         Copyright 02.04.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mincgsetstpmax(mincgstate state, double stpmax)
+    {
+
+        mincg.mincgsetstpmax(state.innerobj, stpmax);
+        return;
+    }
+
+    /*************************************************************************
+    This function allows to suggest initial step length to the CG algorithm.
+
+    Suggested  step  length  is used as starting point for the line search. It
+    can be useful when you have  badly  scaled  problem,  i.e.  when  ||grad||
+    (which is used as initial estimate for the first step) is many  orders  of
+    magnitude different from the desired step.
+
+    Line search  may  fail  on  such problems without good estimate of initial
+    step length. Imagine, for example, problem with ||grad||=10^50 and desired
+    step equal to 0.1 Line  search function will use 10^50  as  initial  step,
+    then  it  will  decrease step length by 2 (up to 20 attempts) and will get
+    10^44, which is still too large.
+
+    This function allows us to tell than line search should  be  started  from
+    some moderate step length, like 1.0, so algorithm will be able  to  detect
+    desired step length in a several searches.
+
+    Default behavior (when no step is suggested) is to use preconditioner,  if
+    it is available, to generate initial estimate of step length.
+
+    This function influences only first iteration of algorithm. It  should  be
+    called between MinCGCreate/MinCGRestartFrom() call and MinCGOptimize call.
+    Suggested step is ignored if you have preconditioner.
+
+    INPUT PARAMETERS:
+        State   -   structure used to store algorithm state.
+        Stp     -   initial estimate of the step length.
+                    Can be zero (no estimate).
+
+      -- ALGLIB --
+         Copyright 30.07.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mincgsuggeststep(mincgstate state, double stp)
+    {
+
+        mincg.mincgsuggeststep(state.innerobj, stp);
+        return;
+    }
+
+    /*************************************************************************
+    Modification of the preconditioner: preconditioning is turned off.
+
+    INPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+
+    NOTE:  you  can  change  preconditioner  "on  the  fly",  during algorithm
+    iterations.
+
+      -- ALGLIB --
+         Copyright 13.10.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mincgsetprecdefault(mincgstate state)
+    {
+
+        mincg.mincgsetprecdefault(state.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    Modification  of  the  preconditioner:  diagonal of approximate Hessian is
+    used.
+
+    INPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+        D       -   diagonal of the approximate Hessian, array[0..N-1],
+                    (if larger, only leading N elements are used).
+
+    NOTE:  you  can  change  preconditioner  "on  the  fly",  during algorithm
+    iterations.
+
+    NOTE 2: D[i] should be positive. Exception will be thrown otherwise.
+
+    NOTE 3: you should pass diagonal of approximate Hessian - NOT ITS INVERSE.
+
+      -- ALGLIB --
+         Copyright 13.10.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mincgsetprecdiag(mincgstate state, double[] d)
+    {
+
+        mincg.mincgsetprecdiag(state.innerobj, d);
+        return;
+    }
+
+    /*************************************************************************
+    Modification of the preconditioner: scale-based diagonal preconditioning.
+
+    This preconditioning mode can be useful when you  don't  have  approximate
+    diagonal of Hessian, but you know that your  variables  are  badly  scaled
+    (for  example,  one  variable is in [1,10], and another in [1000,100000]),
+    and most part of the ill-conditioning comes from different scales of vars.
+
+    In this case simple  scale-based  preconditioner,  with H[i] = 1/(s[i]^2),
+    can greatly improve convergence.
+
+    IMPRTANT: you should set scale of your variables with MinCGSetScale() call
+    (before or after MinCGSetPrecScale() call). Without knowledge of the scale
+    of your variables scale-based preconditioner will be just unit matrix.
+
+    INPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+
+    NOTE:  you  can  change  preconditioner  "on  the  fly",  during algorithm
+    iterations.
+
+      -- ALGLIB --
+         Copyright 13.10.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mincgsetprecscale(mincgstate state)
+    {
+
+        mincg.mincgsetprecscale(state.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    This function provides reverse communication interface
+    Reverse communication interface is not documented or recommended to use.
+    See below for functions which provide better documented API
+    *************************************************************************/
+    public static bool mincgiteration(mincgstate state)
+    {
+
+        bool result = mincg.mincgiteration(state.innerobj);
+        return result;
+    }
+    /*************************************************************************
+    This family of functions is used to launcn iterations of nonlinear optimizer
+
+    These functions accept following parameters:
+        func    -   callback which calculates function (or merit function)
+                    value func at given point x
+        grad    -   callback which calculates function (or merit function)
+                    value func and gradient grad at given point x
+        rep     -   optional callback which is called after each iteration
+                    can be null
+        obj     -   optional object which is passed to func/grad/hess/jac/rep
+                    can be null
+
+    NOTES:
+
+    1. This function has two different implementations: one which  uses  exact
+       (analytical) user-supplied  gradient, and one which uses function value
+       only  and  numerically  differentiates  function  in  order  to  obtain
+       gradient.
+
+       Depending  on  the  specific  function  used to create optimizer object
+       (either MinCGCreate()  for analytical gradient  or  MinCGCreateF()  for
+       numerical differentiation) you should  choose  appropriate  variant  of
+       MinCGOptimize() - one which accepts function AND gradient or one  which
+       accepts function ONLY.
+
+       Be careful to choose variant of MinCGOptimize()  which  corresponds  to
+       your optimization scheme! Table below lists different  combinations  of
+       callback (function/gradient) passed  to  MinCGOptimize()  and  specific
+       function used to create optimizer.
+
+
+                      |         USER PASSED TO MinCGOptimize()
+       CREATED WITH   |  function only   |  function and gradient
+       ------------------------------------------------------------
+       MinCGCreateF() |     work                FAIL
+       MinCGCreate()  |     FAIL                work
+
+       Here "FAIL" denotes inappropriate combinations  of  optimizer  creation
+       function and MinCGOptimize() version. Attemps to use  such  combination
+       (for  example,  to create optimizer with  MinCGCreateF()  and  to  pass
+       gradient information to MinCGOptimize()) will lead to  exception  being
+       thrown. Either  you  did  not  pass  gradient when it WAS needed or you
+       passed gradient when it was NOT needed.
+
+      -- ALGLIB --
+         Copyright 20.04.2009 by Bochkanov Sergey
+
+    *************************************************************************/
+    public static void mincgoptimize(mincgstate state, ndimensional_func func, ndimensional_rep rep, object obj)
+    {
+        if( func==null )
+            throw new alglibexception("ALGLIB: error in 'mincgoptimize()' (func is null)");
+        while( alglib.mincgiteration(state) )
+        {
+            if( state.needf )
+            {
+                func(state.x, ref state.innerobj.f, obj);
+                continue;
+            }
+            if( state.innerobj.xupdated )
+            {
+                if( rep!=null )
+                    rep(state.innerobj.x, state.innerobj.f, obj);
+                continue;
+            }
+            throw new alglibexception("ALGLIB: error in 'mincgoptimize' (some derivatives were not provided?)");
+        }
+    }
+
+
+    public static void mincgoptimize(mincgstate state, ndimensional_grad grad, ndimensional_rep rep, object obj)
+    {
+        if( grad==null )
+            throw new alglibexception("ALGLIB: error in 'mincgoptimize()' (grad is null)");
+        while( alglib.mincgiteration(state) )
+        {
+            if( state.needfg )
+            {
+                grad(state.x, ref state.innerobj.f, state.innerobj.g, obj);
+                continue;
+            }
+            if( state.innerobj.xupdated )
+            {
+                if( rep!=null )
+                    rep(state.innerobj.x, state.innerobj.f, obj);
+                continue;
+            }
+            throw new alglibexception("ALGLIB: error in 'mincgoptimize' (some derivatives were not provided?)");
+        }
+    }
+
+
+
+    /*************************************************************************
+    Conjugate gradient results
+
+    INPUT PARAMETERS:
+        State   -   algorithm state
+
+    OUTPUT PARAMETERS:
+        X       -   array[0..N-1], solution
+        Rep     -   optimization report:
+                    * Rep.TerminationType completetion code:
+                        *  1    relative function improvement is no more than
+                                EpsF.
+                        *  2    relative step is no more than EpsX.
+                        *  4    gradient norm is no more than EpsG
+                        *  5    MaxIts steps was taken
+                        *  7    stopping conditions are too stringent,
+                                further improvement is impossible,
+                                we return best X found so far
+                        *  8    terminated by user
+                    * Rep.IterationsCount contains iterations count
+                    * NFEV countains number of function calculations
+
+      -- ALGLIB --
+         Copyright 20.04.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mincgresults(mincgstate state, out double[] x, out mincgreport rep)
+    {
+        x = new double[0];
+        rep = new mincgreport();
+        mincg.mincgresults(state.innerobj, ref x, rep.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    Conjugate gradient results
+
+    Buffered implementation of MinCGResults(), which uses pre-allocated buffer
+    to store X[]. If buffer size is  too  small,  it  resizes  buffer.  It  is
+    intended to be used in the inner cycles of performance critical algorithms
+    where array reallocation penalty is too large to be ignored.
+
+      -- ALGLIB --
+         Copyright 20.04.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mincgresultsbuf(mincgstate state, ref double[] x, mincgreport rep)
+    {
+
+        mincg.mincgresultsbuf(state.innerobj, ref x, rep.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    This  subroutine  restarts  CG  algorithm from new point. All optimization
+    parameters are left unchanged.
+
+    This  function  allows  to  solve multiple  optimization  problems  (which
+    must have same number of dimensions) without object reallocation penalty.
+
+    INPUT PARAMETERS:
+        State   -   structure used to store algorithm state.
+        X       -   new starting point.
+
+      -- ALGLIB --
+         Copyright 30.07.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mincgrestartfrom(mincgstate state, double[] x)
+    {
+
+        mincg.mincgrestartfrom(state.innerobj, x);
+        return;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    This object stores nonlinear optimizer state.
+    You should use functions provided by MinBLEIC subpackage to work with this
+    object
+    *************************************************************************/
+    public class minbleicstate
+    {
+        //
+        // Public declarations
+        //
+        public bool needf { get { return _innerobj.needf; } set { _innerobj.needf = value; } }
+        public bool needfg { get { return _innerobj.needfg; } set { _innerobj.needfg = value; } }
+        public bool xupdated { get { return _innerobj.xupdated; } set { _innerobj.xupdated = value; } }
+        public double f { get { return _innerobj.f; } set { _innerobj.f = value; } }
+        public double[] g { get { return _innerobj.g; } }
+        public double[] x { get { return _innerobj.x; } }
+
+        public minbleicstate()
+        {
+            _innerobj = new minbleic.minbleicstate();
+        }
+
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private minbleic.minbleicstate _innerobj;
+        public minbleic.minbleicstate innerobj { get { return _innerobj; } }
+        public minbleicstate(minbleic.minbleicstate obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+
+    /*************************************************************************
+    This structure stores optimization report:
+    * InnerIterationsCount      number of inner iterations
+    * OuterIterationsCount      number of outer iterations
+    * NFEV                      number of gradient evaluations
+    * TerminationType           termination type (see below)
+
+    TERMINATION CODES
+
+    TerminationType field contains completion code, which can be:
+      -10   unsupported combination of algorithm settings:
+            1) StpMax is set to non-zero value,
+            AND 2) non-default preconditioner is used.
+            You can't use both features at the same moment,
+            so you have to choose one of them (and to turn
+            off another one).
+      -3    inconsistent constraints. Feasible point is
+            either nonexistent or too hard to find. Try to
+            restart optimizer with better initial
+            approximation
+       4    conditions on constraints are fulfilled
+            with error less than or equal to EpsC
+       5    MaxIts steps was taken
+       7    stopping conditions are too stringent,
+            further improvement is impossible,
+            X contains best point found so far.
+
+    ADDITIONAL FIELDS
+
+    There are additional fields which can be used for debugging:
+    * DebugEqErr                error in the equality constraints (2-norm)
+    * DebugFS                   f, calculated at projection of initial point
+                                to the feasible set
+    * DebugFF                   f, calculated at the final point
+    * DebugDX                   |X_start-X_final|
+    *************************************************************************/
+    public class minbleicreport
+    {
+        //
+        // Public declarations
+        //
+        public int inneriterationscount { get { return _innerobj.inneriterationscount; } set { _innerobj.inneriterationscount = value; } }
+        public int outeriterationscount { get { return _innerobj.outeriterationscount; } set { _innerobj.outeriterationscount = value; } }
+        public int nfev { get { return _innerobj.nfev; } set { _innerobj.nfev = value; } }
+        public int terminationtype { get { return _innerobj.terminationtype; } set { _innerobj.terminationtype = value; } }
+        public double debugeqerr { get { return _innerobj.debugeqerr; } set { _innerobj.debugeqerr = value; } }
+        public double debugfs { get { return _innerobj.debugfs; } set { _innerobj.debugfs = value; } }
+        public double debugff { get { return _innerobj.debugff; } set { _innerobj.debugff = value; } }
+        public double debugdx { get { return _innerobj.debugdx; } set { _innerobj.debugdx = value; } }
+        public int debugfeasqpits { get { return _innerobj.debugfeasqpits; } set { _innerobj.debugfeasqpits = value; } }
+        public int debugfeasgpaits { get { return _innerobj.debugfeasgpaits; } set { _innerobj.debugfeasgpaits = value; } }
+
+        public minbleicreport()
+        {
+            _innerobj = new minbleic.minbleicreport();
+        }
+
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private minbleic.minbleicreport _innerobj;
+        public minbleic.minbleicreport innerobj { get { return _innerobj; } }
+        public minbleicreport(minbleic.minbleicreport obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+    /*************************************************************************
+                         BOUND CONSTRAINED OPTIMIZATION
+           WITH ADDITIONAL LINEAR EQUALITY AND INEQUALITY CONSTRAINTS
+
+    DESCRIPTION:
+    The  subroutine  minimizes  function   F(x)  of N arguments subject to any
+    combination of:
+    * bound constraints
+    * linear inequality constraints
+    * linear equality constraints
+
+    REQUIREMENTS:
+    * user must provide function value and gradient
+    * starting point X0 must be feasible or
+      not too far away from the feasible set
+    * grad(f) must be Lipschitz continuous on a level set:
+      L = { x : f(x)<=f(x0) }
+    * function must be defined everywhere on the feasible set F
+
+    USAGE:
+
+    Constrained optimization if far more complex than the unconstrained one.
+    Here we give very brief outline of the BLEIC optimizer. We strongly recommend
+    you to read examples in the ALGLIB Reference Manual and to read ALGLIB User Guide
+    on optimization, which is available at http://www.alglib.net/optimization/
+
+    1. User initializes algorithm state with MinBLEICCreate() call
+
+    2. USer adds boundary and/or linear constraints by calling
+       MinBLEICSetBC() and MinBLEICSetLC() functions.
+
+    3. User sets stopping conditions for underlying unconstrained solver
+       with MinBLEICSetInnerCond() call.
+       This function controls accuracy of underlying optimization algorithm.
+
+    4. User sets stopping conditions for outer iteration by calling
+       MinBLEICSetOuterCond() function.
+       This function controls handling of boundary and inequality constraints.
+
+    5. Additionally, user may set limit on number of internal iterations
+       by MinBLEICSetMaxIts() call.
+       This function allows to prevent algorithm from looping forever.
+
+    6. User calls MinBLEICOptimize() function which takes algorithm  state and
+       pointer (delegate, etc.) to callback function which calculates F/G.
+
+    7. User calls MinBLEICResults() to get solution
+
+    8. Optionally user may call MinBLEICRestartFrom() to solve another problem
+       with same N but another starting point.
+       MinBLEICRestartFrom() allows to reuse already initialized structure.
+
+
+    INPUT PARAMETERS:
+        N       -   problem dimension, N>0:
+                    * if given, only leading N elements of X are used
+                    * if not given, automatically determined from size ofX
+        X       -   starting point, array[N]:
+                    * it is better to set X to a feasible point
+                    * but X can be infeasible, in which case algorithm will try
+                      to find feasible point first, using X as initial
+                      approximation.
+
+    OUTPUT PARAMETERS:
+        State   -   structure stores algorithm state
+
+      -- ALGLIB --
+         Copyright 28.11.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minbleiccreate(int n, double[] x, out minbleicstate state)
+    {
+        state = new minbleicstate();
+        minbleic.minbleiccreate(n, x, state.innerobj);
+        return;
+    }
+    public static void minbleiccreate(double[] x, out minbleicstate state)
+    {
+        int n;
+
+        state = new minbleicstate();
+        n = ap.len(x);
+        minbleic.minbleiccreate(n, x, state.innerobj);
+
+        return;
+    }
+
+    /*************************************************************************
+    The subroutine is finite difference variant of MinBLEICCreate().  It  uses
+    finite differences in order to differentiate target function.
+
+    Description below contains information which is specific to  this function
+    only. We recommend to read comments on MinBLEICCreate() in  order  to  get
+    more information about creation of BLEIC optimizer.
+
+    INPUT PARAMETERS:
+        N       -   problem dimension, N>0:
+                    * if given, only leading N elements of X are used
+                    * if not given, automatically determined from size of X
+        X       -   starting point, array[0..N-1].
+        DiffStep-   differentiation step, >0
+
+    OUTPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+
+    NOTES:
+    1. algorithm uses 4-point central formula for differentiation.
+    2. differentiation step along I-th axis is equal to DiffStep*S[I] where
+       S[] is scaling vector which can be set by MinBLEICSetScale() call.
+    3. we recommend you to use moderate values of  differentiation  step.  Too
+       large step will result in too large truncation  errors, while too small
+       step will result in too large numerical  errors.  1.0E-6  can  be  good
+       value to start with.
+    4. Numerical  differentiation  is   very   inefficient  -   one   gradient
+       calculation needs 4*N function evaluations. This function will work for
+       any N - either small (1...10), moderate (10...100) or  large  (100...).
+       However, performance penalty will be too severe for any N's except  for
+       small ones.
+       We should also say that code which relies on numerical  differentiation
+       is  less  robust and precise. CG needs exact gradient values. Imprecise
+       gradient may slow  down  convergence, especially  on  highly  nonlinear
+       problems.
+       Thus  we  recommend to use this function for fast prototyping on small-
+       dimensional problems only, and to implement analytical gradient as soon
+       as possible.
+
+      -- ALGLIB --
+         Copyright 16.05.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minbleiccreatef(int n, double[] x, double diffstep, out minbleicstate state)
+    {
+        state = new minbleicstate();
+        minbleic.minbleiccreatef(n, x, diffstep, state.innerobj);
+        return;
+    }
+    public static void minbleiccreatef(double[] x, double diffstep, out minbleicstate state)
+    {
+        int n;
+
+        state = new minbleicstate();
+        n = ap.len(x);
+        minbleic.minbleiccreatef(n, x, diffstep, state.innerobj);
+
+        return;
+    }
+
+    /*************************************************************************
+    This function sets boundary constraints for BLEIC optimizer.
+
+    Boundary constraints are inactive by default (after initial creation).
+    They are preserved after algorithm restart with MinBLEICRestartFrom().
+
+    INPUT PARAMETERS:
+        State   -   structure stores algorithm state
+        BndL    -   lower bounds, array[N].
+                    If some (all) variables are unbounded, you may specify
+                    very small number or -INF.
+        BndU    -   upper bounds, array[N].
+                    If some (all) variables are unbounded, you may specify
+                    very large number or +INF.
+
+    NOTE 1: it is possible to specify BndL[i]=BndU[i]. In this case I-th
+    variable will be "frozen" at X[i]=BndL[i]=BndU[i].
+
+    NOTE 2: this solver has following useful properties:
+    * bound constraints are always satisfied exactly
+    * function is evaluated only INSIDE area specified by  bound  constraints,
+      even  when  numerical  differentiation is used (algorithm adjusts  nodes
+      according to boundary constraints)
+
+      -- ALGLIB --
+         Copyright 28.11.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minbleicsetbc(minbleicstate state, double[] bndl, double[] bndu)
+    {
+
+        minbleic.minbleicsetbc(state.innerobj, bndl, bndu);
+        return;
+    }
+
+    /*************************************************************************
+    This function sets linear constraints for BLEIC optimizer.
+
+    Linear constraints are inactive by default (after initial creation).
+    They are preserved after algorithm restart with MinBLEICRestartFrom().
+
+    INPUT PARAMETERS:
+        State   -   structure previously allocated with MinBLEICCreate call.
+        C       -   linear constraints, array[K,N+1].
+                    Each row of C represents one constraint, either equality
+                    or inequality (see below):
+                    * first N elements correspond to coefficients,
+                    * last element corresponds to the right part.
+                    All elements of C (including right part) must be finite.
+        CT      -   type of constraints, array[K]:
+                    * if CT[i]>0, then I-th constraint is C[i,*]*x >= C[i,n+1]
+                    * if CT[i]=0, then I-th constraint is C[i,*]*x  = C[i,n+1]
+                    * if CT[i]<0, then I-th constraint is C[i,*]*x <= C[i,n+1]
+        K       -   number of equality/inequality constraints, K>=0:
+                    * if given, only leading K elements of C/CT are used
+                    * if not given, automatically determined from sizes of C/CT
+
+    NOTE 1: linear (non-bound) constraints are satisfied only approximately:
+    * there always exists some minor violation (about Epsilon in magnitude)
+      due to rounding errors
+    * numerical differentiation, if used, may  lead  to  function  evaluations
+      outside  of the feasible  area,   because   algorithm  does  NOT  change
+      numerical differentiation formula according to linear constraints.
+    If you want constraints to be  satisfied  exactly, try to reformulate your
+    problem  in  such  manner  that  all constraints will become boundary ones
+    (this kind of constraints is always satisfied exactly, both in  the  final
+    solution and in all intermediate points).
+
+      -- ALGLIB --
+         Copyright 28.11.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minbleicsetlc(minbleicstate state, double[,] c, int[] ct, int k)
+    {
+
+        minbleic.minbleicsetlc(state.innerobj, c, ct, k);
+        return;
+    }
+    public static void minbleicsetlc(minbleicstate state, double[,] c, int[] ct)
+    {
+        int k;
+        if( (ap.rows(c)!=ap.len(ct)))
+            throw new alglibexception("Error while calling 'minbleicsetlc': looks like one of arguments has wrong size");
+
+        k = ap.rows(c);
+        minbleic.minbleicsetlc(state.innerobj, c, ct, k);
+
+        return;
+    }
+
+    /*************************************************************************
+    This function sets stopping conditions for the underlying nonlinear CG
+    optimizer. It controls overall accuracy of solution. These conditions
+    should be strict enough in order for algorithm to converge.
+
+    INPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+        EpsG    -   >=0
+                    The  subroutine  finishes  its  work   if   the  condition
+                    |v|<EpsG is satisfied, where:
+                    * |.| means Euclidian norm
+                    * v - scaled gradient vector, v[i]=g[i]*s[i]
+                    * g - gradient
+                    * s - scaling coefficients set by MinBLEICSetScale()
+        EpsF    -   >=0
+                    The  subroutine  finishes  its work if on k+1-th iteration
+                    the  condition  |F(k+1)-F(k)|<=EpsF*max{|F(k)|,|F(k+1)|,1}
+                    is satisfied.
+        EpsX    -   >=0
+                    The subroutine finishes its work if  on  k+1-th  iteration
+                    the condition |v|<=EpsX is fulfilled, where:
+                    * |.| means Euclidian norm
+                    * v - scaled step vector, v[i]=dx[i]/s[i]
+                    * dx - ste pvector, dx=X(k+1)-X(k)
+                    * s - scaling coefficients set by MinBLEICSetScale()
+
+    Passing EpsG=0, EpsF=0 and EpsX=0 (simultaneously) will lead to
+    automatic stopping criterion selection.
+
+    These conditions are used to terminate inner iterations. However, you
+    need to tune termination conditions for outer iterations too.
+
+      -- ALGLIB --
+         Copyright 28.11.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minbleicsetinnercond(minbleicstate state, double epsg, double epsf, double epsx)
+    {
+
+        minbleic.minbleicsetinnercond(state.innerobj, epsg, epsf, epsx);
+        return;
+    }
+
+    /*************************************************************************
+    This function sets stopping conditions for outer iteration of BLEIC algo.
+
+    These conditions control accuracy of constraint handling and amount of
+    infeasibility allowed in the solution.
+
+    INPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+        EpsX    -   >0, stopping condition on outer iteration step length
+        EpsI    -   >0, stopping condition on infeasibility
+
+    Both EpsX and EpsI must be non-zero.
+
+    MEANING OF EpsX
+
+    EpsX  is  a  stopping  condition for outer iterations. Algorithm will stop
+    when  solution  of  the  current  modified  subproblem will be within EpsX
+    (using 2-norm) of the previous solution.
+
+    MEANING OF EpsI
+
+    EpsI controls feasibility properties -  algorithm  won't  stop  until  all
+    inequality constraints will be satisfied with error (distance from current
+    point to the feasible area) at most EpsI.
+
+      -- ALGLIB --
+         Copyright 28.11.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minbleicsetoutercond(minbleicstate state, double epsx, double epsi)
+    {
+
+        minbleic.minbleicsetoutercond(state.innerobj, epsx, epsi);
+        return;
+    }
+
+    /*************************************************************************
+    This function sets scaling coefficients for BLEIC optimizer.
+
+    ALGLIB optimizers use scaling matrices to test stopping  conditions  (step
+    size and gradient are scaled before comparison with tolerances).  Scale of
+    the I-th variable is a translation invariant measure of:
+    a) "how large" the variable is
+    b) how large the step should be to make significant changes in the function
+
+    Scaling is also used by finite difference variant of the optimizer  - step
+    along I-th axis is equal to DiffStep*S[I].
+
+    In  most  optimizers  (and  in  the  BLEIC  too)  scaling is NOT a form of
+    preconditioning. It just  affects  stopping  conditions.  You  should  set
+    preconditioner  by  separate  call  to  one  of  the  MinBLEICSetPrec...()
+    functions.
+
+    There is a special  preconditioning  mode, however,  which  uses   scaling
+    coefficients to form diagonal preconditioning matrix. You  can  turn  this
+    mode on, if you want.   But  you should understand that scaling is not the
+    same thing as preconditioning - these are two different, although  related
+    forms of tuning solver.
+
+    INPUT PARAMETERS:
+        State   -   structure stores algorithm state
+        S       -   array[N], non-zero scaling coefficients
+                    S[i] may be negative, sign doesn't matter.
+
+      -- ALGLIB --
+         Copyright 14.01.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minbleicsetscale(minbleicstate state, double[] s)
+    {
+
+        minbleic.minbleicsetscale(state.innerobj, s);
+        return;
+    }
+
+    /*************************************************************************
+    Modification of the preconditioner: preconditioning is turned off.
+
+    INPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+
+      -- ALGLIB --
+         Copyright 13.10.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minbleicsetprecdefault(minbleicstate state)
+    {
+
+        minbleic.minbleicsetprecdefault(state.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    Modification  of  the  preconditioner:  diagonal of approximate Hessian is
+    used.
+
+    INPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+        D       -   diagonal of the approximate Hessian, array[0..N-1],
+                    (if larger, only leading N elements are used).
+
+    NOTE 1: D[i] should be positive. Exception will be thrown otherwise.
+
+    NOTE 2: you should pass diagonal of approximate Hessian - NOT ITS INVERSE.
+
+      -- ALGLIB --
+         Copyright 13.10.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minbleicsetprecdiag(minbleicstate state, double[] d)
+    {
+
+        minbleic.minbleicsetprecdiag(state.innerobj, d);
+        return;
+    }
+
+    /*************************************************************************
+    Modification of the preconditioner: scale-based diagonal preconditioning.
+
+    This preconditioning mode can be useful when you  don't  have  approximate
+    diagonal of Hessian, but you know that your  variables  are  badly  scaled
+    (for  example,  one  variable is in [1,10], and another in [1000,100000]),
+    and most part of the ill-conditioning comes from different scales of vars.
+
+    In this case simple  scale-based  preconditioner,  with H[i] = 1/(s[i]^2),
+    can greatly improve convergence.
+
+    IMPRTANT: you should set scale of your variables  with  MinBLEICSetScale()
+    call  (before  or after MinBLEICSetPrecScale() call). Without knowledge of
+    the scale of your variables scale-based preconditioner will be  just  unit
+    matrix.
+
+    INPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+
+      -- ALGLIB --
+         Copyright 13.10.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minbleicsetprecscale(minbleicstate state)
+    {
+
+        minbleic.minbleicsetprecscale(state.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    This function allows to stop algorithm after specified number of inner
+    iterations.
+
+    INPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+        MaxIts  -   maximum number of inner iterations.
+                    If MaxIts=0, the number of iterations is unlimited.
+
+      -- ALGLIB --
+         Copyright 28.11.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minbleicsetmaxits(minbleicstate state, int maxits)
+    {
+
+        minbleic.minbleicsetmaxits(state.innerobj, maxits);
+        return;
+    }
+
+    /*************************************************************************
+    This function turns on/off reporting.
+
+    INPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+        NeedXRep-   whether iteration reports are needed or not
+
+    If NeedXRep is True, algorithm will call rep() callback function if  it is
+    provided to MinBLEICOptimize().
+
+      -- ALGLIB --
+         Copyright 28.11.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minbleicsetxrep(minbleicstate state, bool needxrep)
+    {
+
+        minbleic.minbleicsetxrep(state.innerobj, needxrep);
+        return;
+    }
+
+    /*************************************************************************
+    This function sets maximum step length
+
+    IMPORTANT: this feature is hard to combine with preconditioning. You can't
+    set upper limit on step length, when you solve optimization  problem  with
+    linear (non-boundary) constraints AND preconditioner turned on.
+
+    When  non-boundary  constraints  are  present,  you  have to either a) use
+    preconditioner, or b) use upper limit on step length.  YOU CAN'T USE BOTH!
+    In this case algorithm will terminate with appropriate error code.
+
+    INPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+        StpMax  -   maximum step length, >=0. Set StpMax to 0.0,  if you don't
+                    want to limit step length.
+
+    Use this subroutine when you optimize target function which contains exp()
+    or  other  fast  growing  functions,  and optimization algorithm makes too
+    large  steps  which  lead   to overflow. This function allows us to reject
+    steps  that  are  too  large  (and  therefore  expose  us  to the possible
+    overflow) without actually calculating function value at the x+stp*d.
+
+      -- ALGLIB --
+         Copyright 02.04.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minbleicsetstpmax(minbleicstate state, double stpmax)
+    {
+
+        minbleic.minbleicsetstpmax(state.innerobj, stpmax);
+        return;
+    }
+
+    /*************************************************************************
+    This function provides reverse communication interface
+    Reverse communication interface is not documented or recommended to use.
+    See below for functions which provide better documented API
+    *************************************************************************/
+    public static bool minbleiciteration(minbleicstate state)
+    {
+
+        bool result = minbleic.minbleiciteration(state.innerobj);
+        return result;
+    }
+    /*************************************************************************
+    This family of functions is used to launcn iterations of nonlinear optimizer
+
+    These functions accept following parameters:
+        func    -   callback which calculates function (or merit function)
+                    value func at given point x
+        grad    -   callback which calculates function (or merit function)
+                    value func and gradient grad at given point x
+        rep     -   optional callback which is called after each iteration
+                    can be null
+        obj     -   optional object which is passed to func/grad/hess/jac/rep
+                    can be null
+
+    NOTES:
+
+    1. This function has two different implementations: one which  uses  exact
+       (analytical) user-supplied gradient,  and one which uses function value
+       only  and  numerically  differentiates  function  in  order  to  obtain
+       gradient.
+
+       Depending  on  the  specific  function  used to create optimizer object
+       (either  MinBLEICCreate() for analytical gradient or  MinBLEICCreateF()
+       for numerical differentiation) you should choose appropriate variant of
+       MinBLEICOptimize() - one  which  accepts  function  AND gradient or one
+       which accepts function ONLY.
+
+       Be careful to choose variant of MinBLEICOptimize() which corresponds to
+       your optimization scheme! Table below lists different  combinations  of
+       callback (function/gradient) passed to MinBLEICOptimize()  and specific
+       function used to create optimizer.
+
+
+                         |         USER PASSED TO MinBLEICOptimize()
+       CREATED WITH      |  function only   |  function and gradient
+       ------------------------------------------------------------
+       MinBLEICCreateF() |     work                FAIL
+       MinBLEICCreate()  |     FAIL                work
+
+       Here "FAIL" denotes inappropriate combinations  of  optimizer  creation
+       function  and  MinBLEICOptimize()  version.   Attemps   to   use   such
+       combination (for  example,  to  create optimizer with MinBLEICCreateF()
+       and  to  pass  gradient  information  to  MinCGOptimize()) will lead to
+       exception being thrown. Either  you  did  not pass gradient when it WAS
+       needed or you passed gradient when it was NOT needed.
+
+      -- ALGLIB --
+         Copyright 28.11.2010 by Bochkanov Sergey
+
+    *************************************************************************/
+    public static void minbleicoptimize(minbleicstate state, ndimensional_func func, ndimensional_rep rep, object obj)
+    {
+        if( func==null )
+            throw new alglibexception("ALGLIB: error in 'minbleicoptimize()' (func is null)");
+        while( alglib.minbleiciteration(state) )
+        {
+            if( state.needf )
+            {
+                func(state.x, ref state.innerobj.f, obj);
+                continue;
+            }
+            if( state.innerobj.xupdated )
+            {
+                if( rep!=null )
+                    rep(state.innerobj.x, state.innerobj.f, obj);
+                continue;
+            }
+            throw new alglibexception("ALGLIB: error in 'minbleicoptimize' (some derivatives were not provided?)");
+        }
+    }
+
+
+    public static void minbleicoptimize(minbleicstate state, ndimensional_grad grad, ndimensional_rep rep, object obj)
+    {
+        if( grad==null )
+            throw new alglibexception("ALGLIB: error in 'minbleicoptimize()' (grad is null)");
+        while( alglib.minbleiciteration(state) )
+        {
+            if( state.needfg )
+            {
+                grad(state.x, ref state.innerobj.f, state.innerobj.g, obj);
+                continue;
+            }
+            if( state.innerobj.xupdated )
+            {
+                if( rep!=null )
+                    rep(state.innerobj.x, state.innerobj.f, obj);
+                continue;
+            }
+            throw new alglibexception("ALGLIB: error in 'minbleicoptimize' (some derivatives were not provided?)");
+        }
+    }
+
+
+
+    /*************************************************************************
+    BLEIC results
+
+    INPUT PARAMETERS:
+        State   -   algorithm state
+
+    OUTPUT PARAMETERS:
+        X       -   array[0..N-1], solution
+        Rep     -   optimization report. You should check Rep.TerminationType
+                    in  order  to  distinguish  successful  termination  from
+                    unsuccessful one.
+                    More information about fields of this  structure  can  be
+                    found in the comments on MinBLEICReport datatype.
+
+      -- ALGLIB --
+         Copyright 28.11.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minbleicresults(minbleicstate state, out double[] x, out minbleicreport rep)
+    {
+        x = new double[0];
+        rep = new minbleicreport();
+        minbleic.minbleicresults(state.innerobj, ref x, rep.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    BLEIC results
+
+    Buffered implementation of MinBLEICResults() which uses pre-allocated buffer
+    to store X[]. If buffer size is  too  small,  it  resizes  buffer.  It  is
+    intended to be used in the inner cycles of performance critical algorithms
+    where array reallocation penalty is too large to be ignored.
+
+      -- ALGLIB --
+         Copyright 28.11.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minbleicresultsbuf(minbleicstate state, ref double[] x, minbleicreport rep)
+    {
+
+        minbleic.minbleicresultsbuf(state.innerobj, ref x, rep.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    This subroutine restarts algorithm from new point.
+    All optimization parameters (including constraints) are left unchanged.
+
+    This  function  allows  to  solve multiple  optimization  problems  (which
+    must have  same number of dimensions) without object reallocation penalty.
+
+    INPUT PARAMETERS:
+        State   -   structure previously allocated with MinBLEICCreate call.
+        X       -   new starting point.
+
+      -- ALGLIB --
+         Copyright 28.11.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minbleicrestartfrom(minbleicstate state, double[] x)
+    {
+
+        minbleic.minbleicrestartfrom(state.innerobj, x);
+        return;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+
+    *************************************************************************/
+    public class minlbfgsstate
+    {
+        //
+        // Public declarations
+        //
+        public bool needf { get { return _innerobj.needf; } set { _innerobj.needf = value; } }
+        public bool needfg { get { return _innerobj.needfg; } set { _innerobj.needfg = value; } }
+        public bool xupdated { get { return _innerobj.xupdated; } set { _innerobj.xupdated = value; } }
+        public double f { get { return _innerobj.f; } set { _innerobj.f = value; } }
+        public double[] g { get { return _innerobj.g; } }
+        public double[] x { get { return _innerobj.x; } }
+
+        public minlbfgsstate()
+        {
+            _innerobj = new minlbfgs.minlbfgsstate();
+        }
+
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private minlbfgs.minlbfgsstate _innerobj;
+        public minlbfgs.minlbfgsstate innerobj { get { return _innerobj; } }
+        public minlbfgsstate(minlbfgs.minlbfgsstate obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+
+    /*************************************************************************
+
+    *************************************************************************/
+    public class minlbfgsreport
+    {
+        //
+        // Public declarations
+        //
+        public int iterationscount { get { return _innerobj.iterationscount; } set { _innerobj.iterationscount = value; } }
+        public int nfev { get { return _innerobj.nfev; } set { _innerobj.nfev = value; } }
+        public int terminationtype { get { return _innerobj.terminationtype; } set { _innerobj.terminationtype = value; } }
+
+        public minlbfgsreport()
+        {
+            _innerobj = new minlbfgs.minlbfgsreport();
+        }
+
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private minlbfgs.minlbfgsreport _innerobj;
+        public minlbfgs.minlbfgsreport innerobj { get { return _innerobj; } }
+        public minlbfgsreport(minlbfgs.minlbfgsreport obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+    /*************************************************************************
+            LIMITED MEMORY BFGS METHOD FOR LARGE SCALE OPTIMIZATION
+
+    DESCRIPTION:
+    The subroutine minimizes function F(x) of N arguments by  using  a  quasi-
+    Newton method (LBFGS scheme) which is optimized to use  a  minimum  amount
+    of memory.
+    The subroutine generates the approximation of an inverse Hessian matrix by
+    using information about the last M steps of the algorithm  (instead of N).
+    It lessens a required amount of memory from a value  of  order  N^2  to  a
+    value of order 2*N*M.
+
+
+    REQUIREMENTS:
+    Algorithm will request following information during its operation:
+    * function value F and its gradient G (simultaneously) at given point X
+
+
+    USAGE:
+    1. User initializes algorithm state with MinLBFGSCreate() call
+    2. User tunes solver parameters with MinLBFGSSetCond() MinLBFGSSetStpMax()
+       and other functions
+    3. User calls MinLBFGSOptimize() function which takes algorithm  state and
+       pointer (delegate, etc.) to callback function which calculates F/G.
+    4. User calls MinLBFGSResults() to get solution
+    5. Optionally user may call MinLBFGSRestartFrom() to solve another problem
+       with same N/M but another starting point and/or another function.
+       MinLBFGSRestartFrom() allows to reuse already initialized structure.
+
+
+    INPUT PARAMETERS:
+        N       -   problem dimension. N>0
+        M       -   number of corrections in the BFGS scheme of Hessian
+                    approximation update. Recommended value:  3<=M<=7. The smaller
+                    value causes worse convergence, the bigger will  not  cause  a
+                    considerably better convergence, but will cause a fall in  the
+                    performance. M<=N.
+        X       -   initial solution approximation, array[0..N-1].
+
+
+    OUTPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+
+
+    NOTES:
+    1. you may tune stopping conditions with MinLBFGSSetCond() function
+    2. if target function contains exp() or other fast growing functions,  and
+       optimization algorithm makes too large steps which leads  to  overflow,
+       use MinLBFGSSetStpMax() function to bound algorithm's  steps.  However,
+       L-BFGS rarely needs such a tuning.
+
+
+      -- ALGLIB --
+         Copyright 02.04.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minlbfgscreate(int n, int m, double[] x, out minlbfgsstate state)
+    {
+        state = new minlbfgsstate();
+        minlbfgs.minlbfgscreate(n, m, x, state.innerobj);
+        return;
+    }
+    public static void minlbfgscreate(int m, double[] x, out minlbfgsstate state)
+    {
+        int n;
+
+        state = new minlbfgsstate();
+        n = ap.len(x);
+        minlbfgs.minlbfgscreate(n, m, x, state.innerobj);
+
+        return;
+    }
+
+    /*************************************************************************
+    The subroutine is finite difference variant of MinLBFGSCreate().  It  uses
+    finite differences in order to differentiate target function.
+
+    Description below contains information which is specific to  this function
+    only. We recommend to read comments on MinLBFGSCreate() in  order  to  get
+    more information about creation of LBFGS optimizer.
+
+    INPUT PARAMETERS:
+        N       -   problem dimension, N>0:
+                    * if given, only leading N elements of X are used
+                    * if not given, automatically determined from size of X
+        M       -   number of corrections in the BFGS scheme of Hessian
+                    approximation update. Recommended value:  3<=M<=7. The smaller
+                    value causes worse convergence, the bigger will  not  cause  a
+                    considerably better convergence, but will cause a fall in  the
+                    performance. M<=N.
+        X       -   starting point, array[0..N-1].
+        DiffStep-   differentiation step, >0
+
+    OUTPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+
+    NOTES:
+    1. algorithm uses 4-point central formula for differentiation.
+    2. differentiation step along I-th axis is equal to DiffStep*S[I] where
+       S[] is scaling vector which can be set by MinLBFGSSetScale() call.
+    3. we recommend you to use moderate values of  differentiation  step.  Too
+       large step will result in too large truncation  errors, while too small
+       step will result in too large numerical  errors.  1.0E-6  can  be  good
+       value to start with.
+    4. Numerical  differentiation  is   very   inefficient  -   one   gradient
+       calculation needs 4*N function evaluations. This function will work for
+       any N - either small (1...10), moderate (10...100) or  large  (100...).
+       However, performance penalty will be too severe for any N's except  for
+       small ones.
+       We should also say that code which relies on numerical  differentiation
+       is   less  robust  and  precise.  LBFGS  needs  exact  gradient values.
+       Imprecise gradient may slow  down  convergence,  especially  on  highly
+       nonlinear problems.
+       Thus  we  recommend to use this function for fast prototyping on small-
+       dimensional problems only, and to implement analytical gradient as soon
+       as possible.
+
+      -- ALGLIB --
+         Copyright 16.05.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minlbfgscreatef(int n, int m, double[] x, double diffstep, out minlbfgsstate state)
+    {
+        state = new minlbfgsstate();
+        minlbfgs.minlbfgscreatef(n, m, x, diffstep, state.innerobj);
+        return;
+    }
+    public static void minlbfgscreatef(int m, double[] x, double diffstep, out minlbfgsstate state)
+    {
+        int n;
+
+        state = new minlbfgsstate();
+        n = ap.len(x);
+        minlbfgs.minlbfgscreatef(n, m, x, diffstep, state.innerobj);
+
+        return;
+    }
+
+    /*************************************************************************
+    This function sets stopping conditions for L-BFGS optimization algorithm.
+
+    INPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+        EpsG    -   >=0
+                    The  subroutine  finishes  its  work   if   the  condition
+                    |v|<EpsG is satisfied, where:
+                    * |.| means Euclidian norm
+                    * v - scaled gradient vector, v[i]=g[i]*s[i]
+                    * g - gradient
+                    * s - scaling coefficients set by MinLBFGSSetScale()
+        EpsF    -   >=0
+                    The  subroutine  finishes  its work if on k+1-th iteration
+                    the  condition  |F(k+1)-F(k)|<=EpsF*max{|F(k)|,|F(k+1)|,1}
+                    is satisfied.
+        EpsX    -   >=0
+                    The subroutine finishes its work if  on  k+1-th  iteration
+                    the condition |v|<=EpsX is fulfilled, where:
+                    * |.| means Euclidian norm
+                    * v - scaled step vector, v[i]=dx[i]/s[i]
+                    * dx - ste pvector, dx=X(k+1)-X(k)
+                    * s - scaling coefficients set by MinLBFGSSetScale()
+        MaxIts  -   maximum number of iterations. If MaxIts=0, the  number  of
+                    iterations is unlimited.
+
+    Passing EpsG=0, EpsF=0, EpsX=0 and MaxIts=0 (simultaneously) will lead to
+    automatic stopping criterion selection (small EpsX).
+
+      -- ALGLIB --
+         Copyright 02.04.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minlbfgssetcond(minlbfgsstate state, double epsg, double epsf, double epsx, int maxits)
+    {
+
+        minlbfgs.minlbfgssetcond(state.innerobj, epsg, epsf, epsx, maxits);
+        return;
+    }
+
+    /*************************************************************************
+    This function turns on/off reporting.
+
+    INPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+        NeedXRep-   whether iteration reports are needed or not
+
+    If NeedXRep is True, algorithm will call rep() callback function if  it is
+    provided to MinLBFGSOptimize().
+
+
+      -- ALGLIB --
+         Copyright 02.04.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minlbfgssetxrep(minlbfgsstate state, bool needxrep)
+    {
+
+        minlbfgs.minlbfgssetxrep(state.innerobj, needxrep);
+        return;
+    }
+
+    /*************************************************************************
+    This function sets maximum step length
+
+    INPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+        StpMax  -   maximum step length, >=0. Set StpMax to 0.0 (default),  if
+                    you don't want to limit step length.
+
+    Use this subroutine when you optimize target function which contains exp()
+    or  other  fast  growing  functions,  and optimization algorithm makes too
+    large  steps  which  leads  to overflow. This function allows us to reject
+    steps  that  are  too  large  (and  therefore  expose  us  to the possible
+    overflow) without actually calculating function value at the x+stp*d.
+
+      -- ALGLIB --
+         Copyright 02.04.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minlbfgssetstpmax(minlbfgsstate state, double stpmax)
+    {
+
+        minlbfgs.minlbfgssetstpmax(state.innerobj, stpmax);
+        return;
+    }
+
+    /*************************************************************************
+    This function sets scaling coefficients for LBFGS optimizer.
+
+    ALGLIB optimizers use scaling matrices to test stopping  conditions  (step
+    size and gradient are scaled before comparison with tolerances).  Scale of
+    the I-th variable is a translation invariant measure of:
+    a) "how large" the variable is
+    b) how large the step should be to make significant changes in the function
+
+    Scaling is also used by finite difference variant of the optimizer  - step
+    along I-th axis is equal to DiffStep*S[I].
+
+    In  most  optimizers  (and  in  the  LBFGS  too)  scaling is NOT a form of
+    preconditioning. It just  affects  stopping  conditions.  You  should  set
+    preconditioner  by  separate  call  to  one  of  the  MinLBFGSSetPrec...()
+    functions.
+
+    There  is  special  preconditioning  mode, however,  which  uses   scaling
+    coefficients to form diagonal preconditioning matrix. You  can  turn  this
+    mode on, if you want.   But  you should understand that scaling is not the
+    same thing as preconditioning - these are two different, although  related
+    forms of tuning solver.
+
+    INPUT PARAMETERS:
+        State   -   structure stores algorithm state
+        S       -   array[N], non-zero scaling coefficients
+                    S[i] may be negative, sign doesn't matter.
+
+      -- ALGLIB --
+         Copyright 14.01.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minlbfgssetscale(minlbfgsstate state, double[] s)
+    {
+
+        minlbfgs.minlbfgssetscale(state.innerobj, s);
+        return;
+    }
+
+    /*************************************************************************
+    Modification  of  the  preconditioner:  default  preconditioner    (simple
+    scaling, same for all elements of X) is used.
+
+    INPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+
+    NOTE:  you  can  change  preconditioner  "on  the  fly",  during algorithm
+    iterations.
+
+      -- ALGLIB --
+         Copyright 13.10.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minlbfgssetprecdefault(minlbfgsstate state)
+    {
+
+        minlbfgs.minlbfgssetprecdefault(state.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    Modification of the preconditioner: Cholesky factorization of  approximate
+    Hessian is used.
+
+    INPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+        P       -   triangular preconditioner, Cholesky factorization of
+                    the approximate Hessian. array[0..N-1,0..N-1],
+                    (if larger, only leading N elements are used).
+        IsUpper -   whether upper or lower triangle of P is given
+                    (other triangle is not referenced)
+
+    After call to this function preconditioner is changed to P  (P  is  copied
+    into the internal buffer).
+
+    NOTE:  you  can  change  preconditioner  "on  the  fly",  during algorithm
+    iterations.
+
+    NOTE 2:  P  should  be nonsingular. Exception will be thrown otherwise.
+
+      -- ALGLIB --
+         Copyright 13.10.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minlbfgssetpreccholesky(minlbfgsstate state, double[,] p, bool isupper)
+    {
+
+        minlbfgs.minlbfgssetpreccholesky(state.innerobj, p, isupper);
+        return;
+    }
+
+    /*************************************************************************
+    Modification  of  the  preconditioner:  diagonal of approximate Hessian is
+    used.
+
+    INPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+        D       -   diagonal of the approximate Hessian, array[0..N-1],
+                    (if larger, only leading N elements are used).
+
+    NOTE:  you  can  change  preconditioner  "on  the  fly",  during algorithm
+    iterations.
+
+    NOTE 2: D[i] should be positive. Exception will be thrown otherwise.
+
+    NOTE 3: you should pass diagonal of approximate Hessian - NOT ITS INVERSE.
+
+      -- ALGLIB --
+         Copyright 13.10.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minlbfgssetprecdiag(minlbfgsstate state, double[] d)
+    {
+
+        minlbfgs.minlbfgssetprecdiag(state.innerobj, d);
+        return;
+    }
+
+    /*************************************************************************
+    Modification of the preconditioner: scale-based diagonal preconditioning.
+
+    This preconditioning mode can be useful when you  don't  have  approximate
+    diagonal of Hessian, but you know that your  variables  are  badly  scaled
+    (for  example,  one  variable is in [1,10], and another in [1000,100000]),
+    and most part of the ill-conditioning comes from different scales of vars.
+
+    In this case simple  scale-based  preconditioner,  with H[i] = 1/(s[i]^2),
+    can greatly improve convergence.
+
+    IMPRTANT: you should set scale of your variables  with  MinLBFGSSetScale()
+    call  (before  or after MinLBFGSSetPrecScale() call). Without knowledge of
+    the scale of your variables scale-based preconditioner will be  just  unit
+    matrix.
+
+    INPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+
+      -- ALGLIB --
+         Copyright 13.10.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minlbfgssetprecscale(minlbfgsstate state)
+    {
+
+        minlbfgs.minlbfgssetprecscale(state.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    This function provides reverse communication interface
+    Reverse communication interface is not documented or recommended to use.
+    See below for functions which provide better documented API
+    *************************************************************************/
+    public static bool minlbfgsiteration(minlbfgsstate state)
+    {
+
+        bool result = minlbfgs.minlbfgsiteration(state.innerobj);
+        return result;
+    }
+    /*************************************************************************
+    This family of functions is used to launcn iterations of nonlinear optimizer
+
+    These functions accept following parameters:
+        func    -   callback which calculates function (or merit function)
+                    value func at given point x
+        grad    -   callback which calculates function (or merit function)
+                    value func and gradient grad at given point x
+        rep     -   optional callback which is called after each iteration
+                    can be null
+        obj     -   optional object which is passed to func/grad/hess/jac/rep
+                    can be null
+
+    NOTES:
+
+    1. This function has two different implementations: one which  uses  exact
+       (analytical) user-supplied gradient,  and one which uses function value
+       only  and  numerically  differentiates  function  in  order  to  obtain
+       gradient.
+
+       Depending  on  the  specific  function  used to create optimizer object
+       (either MinLBFGSCreate() for analytical gradient  or  MinLBFGSCreateF()
+       for numerical differentiation) you should choose appropriate variant of
+       MinLBFGSOptimize() - one  which  accepts  function  AND gradient or one
+       which accepts function ONLY.
+
+       Be careful to choose variant of MinLBFGSOptimize() which corresponds to
+       your optimization scheme! Table below lists different  combinations  of
+       callback (function/gradient) passed to MinLBFGSOptimize()  and specific
+       function used to create optimizer.
+
+
+                         |         USER PASSED TO MinLBFGSOptimize()
+       CREATED WITH      |  function only   |  function and gradient
+       ------------------------------------------------------------
+       MinLBFGSCreateF() |     work                FAIL
+       MinLBFGSCreate()  |     FAIL                work
+
+       Here "FAIL" denotes inappropriate combinations  of  optimizer  creation
+       function  and  MinLBFGSOptimize()  version.   Attemps   to   use   such
+       combination (for example, to create optimizer with MinLBFGSCreateF() and
+       to pass gradient information to MinCGOptimize()) will lead to exception
+       being thrown. Either  you  did  not pass gradient when it WAS needed or
+       you passed gradient when it was NOT needed.
+
+      -- ALGLIB --
+         Copyright 20.03.2009 by Bochkanov Sergey
+
+    *************************************************************************/
+    public static void minlbfgsoptimize(minlbfgsstate state, ndimensional_func func, ndimensional_rep rep, object obj)
+    {
+        if( func==null )
+            throw new alglibexception("ALGLIB: error in 'minlbfgsoptimize()' (func is null)");
+        while( alglib.minlbfgsiteration(state) )
+        {
+            if( state.needf )
+            {
+                func(state.x, ref state.innerobj.f, obj);
+                continue;
+            }
+            if( state.innerobj.xupdated )
+            {
+                if( rep!=null )
+                    rep(state.innerobj.x, state.innerobj.f, obj);
+                continue;
+            }
+            throw new alglibexception("ALGLIB: error in 'minlbfgsoptimize' (some derivatives were not provided?)");
+        }
+    }
+
+
+    public static void minlbfgsoptimize(minlbfgsstate state, ndimensional_grad grad, ndimensional_rep rep, object obj)
+    {
+        if( grad==null )
+            throw new alglibexception("ALGLIB: error in 'minlbfgsoptimize()' (grad is null)");
+        while( alglib.minlbfgsiteration(state) )
+        {
+            if( state.needfg )
+            {
+                grad(state.x, ref state.innerobj.f, state.innerobj.g, obj);
+                continue;
+            }
+            if( state.innerobj.xupdated )
+            {
+                if( rep!=null )
+                    rep(state.innerobj.x, state.innerobj.f, obj);
+                continue;
+            }
+            throw new alglibexception("ALGLIB: error in 'minlbfgsoptimize' (some derivatives were not provided?)");
+        }
+    }
+
+
+
+    /*************************************************************************
+    L-BFGS algorithm results
+
+    INPUT PARAMETERS:
+        State   -   algorithm state
+
+    OUTPUT PARAMETERS:
+        X       -   array[0..N-1], solution
+        Rep     -   optimization report:
+                    * Rep.TerminationType completetion code:
+                        * -2    rounding errors prevent further improvement.
+                                X contains best point found.
+                        * -1    incorrect parameters were specified
+                        *  1    relative function improvement is no more than
+                                EpsF.
+                        *  2    relative step is no more than EpsX.
+                        *  4    gradient norm is no more than EpsG
+                        *  5    MaxIts steps was taken
+                        *  7    stopping conditions are too stringent,
+                                further improvement is impossible
+                    * Rep.IterationsCount contains iterations count
+                    * NFEV countains number of function calculations
+
+      -- ALGLIB --
+         Copyright 02.04.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minlbfgsresults(minlbfgsstate state, out double[] x, out minlbfgsreport rep)
+    {
+        x = new double[0];
+        rep = new minlbfgsreport();
+        minlbfgs.minlbfgsresults(state.innerobj, ref x, rep.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    L-BFGS algorithm results
+
+    Buffered implementation of MinLBFGSResults which uses pre-allocated buffer
+    to store X[]. If buffer size is  too  small,  it  resizes  buffer.  It  is
+    intended to be used in the inner cycles of performance critical algorithms
+    where array reallocation penalty is too large to be ignored.
+
+      -- ALGLIB --
+         Copyright 20.08.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minlbfgsresultsbuf(minlbfgsstate state, ref double[] x, minlbfgsreport rep)
+    {
+
+        minlbfgs.minlbfgsresultsbuf(state.innerobj, ref x, rep.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    This  subroutine restarts LBFGS algorithm from new point. All optimization
+    parameters are left unchanged.
+
+    This  function  allows  to  solve multiple  optimization  problems  (which
+    must have same number of dimensions) without object reallocation penalty.
+
+    INPUT PARAMETERS:
+        State   -   structure used to store algorithm state
+        X       -   new starting point.
+
+      -- ALGLIB --
+         Copyright 30.07.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minlbfgsrestartfrom(minlbfgsstate state, double[] x)
+    {
+
+        minlbfgs.minlbfgsrestartfrom(state.innerobj, x);
+        return;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    This object stores nonlinear optimizer state.
+    You should use functions provided by MinQP subpackage to work with this
+    object
+    *************************************************************************/
+    public class minqpstate
+    {
+        //
+        // Public declarations
+        //
+
+        public minqpstate()
+        {
+            _innerobj = new minqp.minqpstate();
+        }
+
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private minqp.minqpstate _innerobj;
+        public minqp.minqpstate innerobj { get { return _innerobj; } }
+        public minqpstate(minqp.minqpstate obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+
+    /*************************************************************************
+    This structure stores optimization report:
+    * InnerIterationsCount      number of inner iterations
+    * OuterIterationsCount      number of outer iterations
+    * NCholesky                 number of Cholesky decomposition
+    * NMV                       number of matrix-vector products
+                                (only products calculated as part of iterative
+                                process are counted)
+    * TerminationType           completion code (see below)
+
+    Completion codes:
+    * -5    inappropriate solver was used:
+            * Cholesky solver for semidefinite or indefinite problems
+            * Cholesky solver for problems with non-boundary constraints
+    * -3    inconsistent constraints (or, maybe, feasible point is
+            too hard to find). If you are sure that constraints are feasible,
+            try to restart optimizer with better initial approximation.
+    * -1    solver error
+    *  4    successful completion
+    *  5    MaxIts steps was taken
+    *  7    stopping conditions are too stringent,
+            further improvement is impossible,
+            X contains best point found so far.
+    *************************************************************************/
+    public class minqpreport
+    {
+        //
+        // Public declarations
+        //
+        public int inneriterationscount { get { return _innerobj.inneriterationscount; } set { _innerobj.inneriterationscount = value; } }
+        public int outeriterationscount { get { return _innerobj.outeriterationscount; } set { _innerobj.outeriterationscount = value; } }
+        public int nmv { get { return _innerobj.nmv; } set { _innerobj.nmv = value; } }
+        public int ncholesky { get { return _innerobj.ncholesky; } set { _innerobj.ncholesky = value; } }
+        public int terminationtype { get { return _innerobj.terminationtype; } set { _innerobj.terminationtype = value; } }
+
+        public minqpreport()
+        {
+            _innerobj = new minqp.minqpreport();
+        }
+
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private minqp.minqpreport _innerobj;
+        public minqp.minqpreport innerobj { get { return _innerobj; } }
+        public minqpreport(minqp.minqpreport obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+    /*************************************************************************
+                        CONSTRAINED QUADRATIC PROGRAMMING
+
+    The subroutine creates QP optimizer. After initial creation,  it  contains
+    default optimization problem with zero quadratic and linear terms  and  no
+    constraints. You should set quadratic/linear terms with calls to functions
+    provided by MinQP subpackage.
+
+    INPUT PARAMETERS:
+        N       -   problem size
+
+    OUTPUT PARAMETERS:
+        State   -   optimizer with zero quadratic/linear terms
+                    and no constraints
+
+      -- ALGLIB --
+         Copyright 11.01.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minqpcreate(int n, out minqpstate state)
+    {
+        state = new minqpstate();
+        minqp.minqpcreate(n, state.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    This function sets linear term for QP solver.
+
+    By default, linear term is zero.
+
+    INPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+        B       -   linear term, array[N].
+
+      -- ALGLIB --
+         Copyright 11.01.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minqpsetlinearterm(minqpstate state, double[] b)
+    {
+
+        minqp.minqpsetlinearterm(state.innerobj, b);
+        return;
+    }
+
+    /*************************************************************************
+    This function sets quadratic term for QP solver.
+
+    By default quadratic term is zero.
+
+    IMPORTANT: this solver minimizes following  function:
+        f(x) = 0.5*x'*A*x + b'*x.
+    Note that quadratic term has 0.5 before it. So if  you  want  to  minimize
+        f(x) = x^2 + x
+    you should rewrite your problem as follows:
+        f(x) = 0.5*(2*x^2) + x
+    and your matrix A will be equal to [[2.0]], not to [[1.0]]
+
+    INPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+        A       -   matrix, array[N,N]
+        IsUpper -   (optional) storage type:
+                    * if True, symmetric matrix  A  is  given  by  its  upper
+                      triangle, and the lower triangle isnt used
+                    * if False, symmetric matrix  A  is  given  by  its lower
+                      triangle, and the upper triangle isnt used
+                    * if not given, both lower and upper  triangles  must  be
+                      filled.
+
+      -- ALGLIB --
+         Copyright 11.01.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minqpsetquadraticterm(minqpstate state, double[,] a, bool isupper)
+    {
+
+        minqp.minqpsetquadraticterm(state.innerobj, a, isupper);
+        return;
+    }
+    public static void minqpsetquadraticterm(minqpstate state, double[,] a)
+    {
+        bool isupper;
+        if( !alglib.ap.issymmetric(a) )
+            throw new alglibexception("'a' parameter is not symmetric matrix");
+
+        isupper = false;
+        minqp.minqpsetquadraticterm(state.innerobj, a, isupper);
+
+        return;
+    }
+
+    /*************************************************************************
+    This function sets starting point for QP solver. It is useful to have
+    good initial approximation to the solution, because it will increase
+    speed of convergence and identification of active constraints.
+
+    INPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+        X       -   starting point, array[N].
+
+      -- ALGLIB --
+         Copyright 11.01.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minqpsetstartingpoint(minqpstate state, double[] x)
+    {
+
+        minqp.minqpsetstartingpoint(state.innerobj, x);
+        return;
+    }
+
+    /*************************************************************************
+    This  function sets origin for QP solver. By default, following QP program
+    is solved:
+
+        min(0.5*x'*A*x+b'*x)
+
+    This function allows to solve different problem:
+
+        min(0.5*(x-x_origin)'*A*(x-x_origin)+b'*(x-x_origin))
+
+    INPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+        XOrigin -   origin, array[N].
+
+      -- ALGLIB --
+         Copyright 11.01.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minqpsetorigin(minqpstate state, double[] xorigin)
+    {
+
+        minqp.minqpsetorigin(state.innerobj, xorigin);
+        return;
+    }
+
+    /*************************************************************************
+    This function tells solver to use Cholesky-based algorithm.
+
+    Cholesky-based algorithm can be used when:
+    * problem is convex
+    * there is no constraints or only boundary constraints are present
+
+    This algorithm has O(N^3) complexity for unconstrained problem and  is  up
+    to several times slower on bound constrained  problems  (these  additional
+    iterations are needed to identify active constraints).
+
+    INPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+
+      -- ALGLIB --
+         Copyright 11.01.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minqpsetalgocholesky(minqpstate state)
+    {
+
+        minqp.minqpsetalgocholesky(state.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    This function sets boundary constraints for QP solver
+
+    Boundary constraints are inactive by default (after initial creation).
+    After  being  set,  they  are  preserved  until explicitly turned off with
+    another SetBC() call.
+
+    INPUT PARAMETERS:
+        State   -   structure stores algorithm state
+        BndL    -   lower bounds, array[N].
+                    If some (all) variables are unbounded, you may specify
+                    very small number or -INF (latter is recommended because
+                    it will allow solver to use better algorithm).
+        BndU    -   upper bounds, array[N].
+                    If some (all) variables are unbounded, you may specify
+                    very large number or +INF (latter is recommended because
+                    it will allow solver to use better algorithm).
+
+    NOTE: it is possible to specify BndL[i]=BndU[i]. In this case I-th
+    variable will be "frozen" at X[i]=BndL[i]=BndU[i].
+
+      -- ALGLIB --
+         Copyright 11.01.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minqpsetbc(minqpstate state, double[] bndl, double[] bndu)
+    {
+
+        minqp.minqpsetbc(state.innerobj, bndl, bndu);
+        return;
+    }
+
+    /*************************************************************************
+    This function solves quadratic programming problem.
+    You should call it after setting solver options with MinQPSet...() calls.
+
+    INPUT PARAMETERS:
+        State   -   algorithm state
+
+    You should use MinQPResults() function to access results after calls
+    to this function.
+
+      -- ALGLIB --
+         Copyright 11.01.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minqpoptimize(minqpstate state)
+    {
+
+        minqp.minqpoptimize(state.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    QP solver results
+
+    INPUT PARAMETERS:
+        State   -   algorithm state
+
+    OUTPUT PARAMETERS:
+        X       -   array[0..N-1], solution
+        Rep     -   optimization report. You should check Rep.TerminationType,
+                    which contains completion code, and you may check  another
+                    fields which contain another information  about  algorithm
+                    functioning.
+
+      -- ALGLIB --
+         Copyright 11.01.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minqpresults(minqpstate state, out double[] x, out minqpreport rep)
+    {
+        x = new double[0];
+        rep = new minqpreport();
+        minqp.minqpresults(state.innerobj, ref x, rep.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    QP results
+
+    Buffered implementation of MinQPResults() which uses pre-allocated  buffer
+    to store X[]. If buffer size is  too  small,  it  resizes  buffer.  It  is
+    intended to be used in the inner cycles of performance critical algorithms
+    where array reallocation penalty is too large to be ignored.
+
+      -- ALGLIB --
+         Copyright 11.01.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minqpresultsbuf(minqpstate state, ref double[] x, minqpreport rep)
+    {
+
+        minqp.minqpresultsbuf(state.innerobj, ref x, rep.innerobj);
+        return;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    Levenberg-Marquardt optimizer.
+
+    This structure should be created using one of the MinLMCreate???()
+    functions. You should not access its fields directly; use ALGLIB functions
+    to work with it.
+    *************************************************************************/
+    public class minlmstate
+    {
+        //
+        // Public declarations
+        //
+        public bool needf { get { return _innerobj.needf; } set { _innerobj.needf = value; } }
+        public bool needfg { get { return _innerobj.needfg; } set { _innerobj.needfg = value; } }
+        public bool needfgh { get { return _innerobj.needfgh; } set { _innerobj.needfgh = value; } }
+        public bool needfi { get { return _innerobj.needfi; } set { _innerobj.needfi = value; } }
+        public bool needfij { get { return _innerobj.needfij; } set { _innerobj.needfij = value; } }
+        public bool xupdated { get { return _innerobj.xupdated; } set { _innerobj.xupdated = value; } }
+        public double f { get { return _innerobj.f; } set { _innerobj.f = value; } }
+        public double[] fi { get { return _innerobj.fi; } }
+        public double[] g { get { return _innerobj.g; } }
+        public double[,] h { get { return _innerobj.h; } }
+        public double[,] j { get { return _innerobj.j; } }
+        public double[] x { get { return _innerobj.x; } }
+
+        public minlmstate()
+        {
+            _innerobj = new minlm.minlmstate();
+        }
+
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private minlm.minlmstate _innerobj;
+        public minlm.minlmstate innerobj { get { return _innerobj; } }
+        public minlmstate(minlm.minlmstate obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+
+    /*************************************************************************
+    Optimization report, filled by MinLMResults() function
+
+    FIELDS:
+    * TerminationType, completetion code:
+        * -9    derivative correctness check failed;
+                see Rep.WrongNum, Rep.WrongI, Rep.WrongJ for
+                more information.
+        *  1    relative function improvement is no more than
+                EpsF.
+        *  2    relative step is no more than EpsX.
+        *  4    gradient is no more than EpsG.
+        *  5    MaxIts steps was taken
+        *  7    stopping conditions are too stringent,
+                further improvement is impossible
+    * IterationsCount, contains iterations count
+    * NFunc, number of function calculations
+    * NJac, number of Jacobi matrix calculations
+    * NGrad, number of gradient calculations
+    * NHess, number of Hessian calculations
+    * NCholesky, number of Cholesky decomposition calculations
+    *************************************************************************/
+    public class minlmreport
+    {
+        //
+        // Public declarations
+        //
+        public int iterationscount { get { return _innerobj.iterationscount; } set { _innerobj.iterationscount = value; } }
+        public int terminationtype { get { return _innerobj.terminationtype; } set { _innerobj.terminationtype = value; } }
+        public int nfunc { get { return _innerobj.nfunc; } set { _innerobj.nfunc = value; } }
+        public int njac { get { return _innerobj.njac; } set { _innerobj.njac = value; } }
+        public int ngrad { get { return _innerobj.ngrad; } set { _innerobj.ngrad = value; } }
+        public int nhess { get { return _innerobj.nhess; } set { _innerobj.nhess = value; } }
+        public int ncholesky { get { return _innerobj.ncholesky; } set { _innerobj.ncholesky = value; } }
+
+        public minlmreport()
+        {
+            _innerobj = new minlm.minlmreport();
+        }
+
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private minlm.minlmreport _innerobj;
+        public minlm.minlmreport innerobj { get { return _innerobj; } }
+        public minlmreport(minlm.minlmreport obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+    /*************************************************************************
+                    IMPROVED LEVENBERG-MARQUARDT METHOD FOR
+                     NON-LINEAR LEAST SQUARES OPTIMIZATION
+
+    DESCRIPTION:
+    This function is used to find minimum of function which is represented  as
+    sum of squares:
+        F(x) = f[0]^2(x[0],...,x[n-1]) + ... + f[m-1]^2(x[0],...,x[n-1])
+    using value of function vector f[] and Jacobian of f[].
+
+
+    REQUIREMENTS:
+    This algorithm will request following information during its operation:
+
+    * function vector f[] at given point X
+    * function vector f[] and Jacobian of f[] (simultaneously) at given point
+
+    There are several overloaded versions of  MinLMOptimize()  function  which
+    correspond  to  different LM-like optimization algorithms provided by this
+    unit. You should choose version which accepts fvec()  and jac() callbacks.
+    First  one  is used to calculate f[] at given point, second one calculates
+    f[] and Jacobian df[i]/dx[j].
+
+    You can try to initialize MinLMState structure with VJ  function and  then
+    use incorrect version  of  MinLMOptimize()  (for  example,  version  which
+    works  with  general  form function and does not provide Jacobian), but it
+    will  lead  to  exception  being  thrown  after first attempt to calculate
+    Jacobian.
+
+
+    USAGE:
+    1. User initializes algorithm state with MinLMCreateVJ() call
+    2. User tunes solver parameters with MinLMSetCond(),  MinLMSetStpMax() and
+       other functions
+    3. User calls MinLMOptimize() function which  takes algorithm  state   and
+       callback functions.
+    4. User calls MinLMResults() to get solution
+    5. Optionally, user may call MinLMRestartFrom() to solve  another  problem
+       with same N/M but another starting point and/or another function.
+       MinLMRestartFrom() allows to reuse already initialized structure.
+
+
+    INPUT PARAMETERS:
+        N       -   dimension, N>1
+                    * if given, only leading N elements of X are used
+                    * if not given, automatically determined from size of X
+        M       -   number of functions f[i]
+        X       -   initial solution, array[0..N-1]
+
+    OUTPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+
+    NOTES:
+    1. you may tune stopping conditions with MinLMSetCond() function
+    2. if target function contains exp() or other fast growing functions,  and
+       optimization algorithm makes too large steps which leads  to  overflow,
+       use MinLMSetStpMax() function to bound algorithm's steps.
+
+      -- ALGLIB --
+         Copyright 30.03.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minlmcreatevj(int n, int m, double[] x, out minlmstate state)
+    {
+        state = new minlmstate();
+        minlm.minlmcreatevj(n, m, x, state.innerobj);
+        return;
+    }
+    public static void minlmcreatevj(int m, double[] x, out minlmstate state)
+    {
+        int n;
+
+        state = new minlmstate();
+        n = ap.len(x);
+        minlm.minlmcreatevj(n, m, x, state.innerobj);
+
+        return;
+    }
+
+    /*************************************************************************
+                    IMPROVED LEVENBERG-MARQUARDT METHOD FOR
+                     NON-LINEAR LEAST SQUARES OPTIMIZATION
+
+    DESCRIPTION:
+    This function is used to find minimum of function which is represented  as
+    sum of squares:
+        F(x) = f[0]^2(x[0],...,x[n-1]) + ... + f[m-1]^2(x[0],...,x[n-1])
+    using value of function vector f[] only. Finite differences  are  used  to
+    calculate Jacobian.
+
+
+    REQUIREMENTS:
+    This algorithm will request following information during its operation:
+    * function vector f[] at given point X
+
+    There are several overloaded versions of  MinLMOptimize()  function  which
+    correspond  to  different LM-like optimization algorithms provided by this
+    unit. You should choose version which accepts fvec() callback.
+
+    You can try to initialize MinLMState structure with VJ  function and  then
+    use incorrect version  of  MinLMOptimize()  (for  example,  version  which
+    works with general form function and does not accept function vector), but
+    it will  lead  to  exception being thrown after first attempt to calculate
+    Jacobian.
+
+
+    USAGE:
+    1. User initializes algorithm state with MinLMCreateV() call
+    2. User tunes solver parameters with MinLMSetCond(),  MinLMSetStpMax() and
+       other functions
+    3. User calls MinLMOptimize() function which  takes algorithm  state   and
+       callback functions.
+    4. User calls MinLMResults() to get solution
+    5. Optionally, user may call MinLMRestartFrom() to solve  another  problem
+       with same N/M but another starting point and/or another function.
+       MinLMRestartFrom() allows to reuse already initialized structure.
+
+
+    INPUT PARAMETERS:
+        N       -   dimension, N>1
+                    * if given, only leading N elements of X are used
+                    * if not given, automatically determined from size of X
+        M       -   number of functions f[i]
+        X       -   initial solution, array[0..N-1]
+        DiffStep-   differentiation step, >0
+
+    OUTPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+
+    See also MinLMIteration, MinLMResults.
+
+    NOTES:
+    1. you may tune stopping conditions with MinLMSetCond() function
+    2. if target function contains exp() or other fast growing functions,  and
+       optimization algorithm makes too large steps which leads  to  overflow,
+       use MinLMSetStpMax() function to bound algorithm's steps.
+
+      -- ALGLIB --
+         Copyright 30.03.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minlmcreatev(int n, int m, double[] x, double diffstep, out minlmstate state)
+    {
+        state = new minlmstate();
+        minlm.minlmcreatev(n, m, x, diffstep, state.innerobj);
+        return;
+    }
+    public static void minlmcreatev(int m, double[] x, double diffstep, out minlmstate state)
+    {
+        int n;
+
+        state = new minlmstate();
+        n = ap.len(x);
+        minlm.minlmcreatev(n, m, x, diffstep, state.innerobj);
+
+        return;
+    }
+
+    /*************************************************************************
+        LEVENBERG-MARQUARDT-LIKE METHOD FOR NON-LINEAR OPTIMIZATION
+
+    DESCRIPTION:
+    This  function  is  used  to  find  minimum  of general form (not "sum-of-
+    -squares") function
+        F = F(x[0], ..., x[n-1])
+    using  its  gradient  and  Hessian.  Levenberg-Marquardt modification with
+    L-BFGS pre-optimization and internal pre-conditioned  L-BFGS  optimization
+    after each Levenberg-Marquardt step is used.
+
+
+    REQUIREMENTS:
+    This algorithm will request following information during its operation:
+
+    * function value F at given point X
+    * F and gradient G (simultaneously) at given point X
+    * F, G and Hessian H (simultaneously) at given point X
+
+    There are several overloaded versions of  MinLMOptimize()  function  which
+    correspond  to  different LM-like optimization algorithms provided by this
+    unit. You should choose version which accepts func(),  grad()  and  hess()
+    function pointers. First pointer is used to calculate F  at  given  point,
+    second  one  calculates  F(x)  and  grad F(x),  third one calculates F(x),
+    grad F(x), hess F(x).
+
+    You can try to initialize MinLMState structure with FGH-function and  then
+    use incorrect version of MinLMOptimize() (for example, version which  does
+    not provide Hessian matrix), but it will lead to  exception  being  thrown
+    after first attempt to calculate Hessian.
+
+
+    USAGE:
+    1. User initializes algorithm state with MinLMCreateFGH() call
+    2. User tunes solver parameters with MinLMSetCond(),  MinLMSetStpMax() and
+       other functions
+    3. User calls MinLMOptimize() function which  takes algorithm  state   and
+       pointers (delegates, etc.) to callback functions.
+    4. User calls MinLMResults() to get solution
+    5. Optionally, user may call MinLMRestartFrom() to solve  another  problem
+       with same N but another starting point and/or another function.
+       MinLMRestartFrom() allows to reuse already initialized structure.
+
+
+    INPUT PARAMETERS:
+        N       -   dimension, N>1
+                    * if given, only leading N elements of X are used
+                    * if not given, automatically determined from size of X
+        X       -   initial solution, array[0..N-1]
+
+    OUTPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+
+    NOTES:
+    1. you may tune stopping conditions with MinLMSetCond() function
+    2. if target function contains exp() or other fast growing functions,  and
+       optimization algorithm makes too large steps which leads  to  overflow,
+       use MinLMSetStpMax() function to bound algorithm's steps.
+
+      -- ALGLIB --
+         Copyright 30.03.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minlmcreatefgh(int n, double[] x, out minlmstate state)
+    {
+        state = new minlmstate();
+        minlm.minlmcreatefgh(n, x, state.innerobj);
+        return;
+    }
+    public static void minlmcreatefgh(double[] x, out minlmstate state)
+    {
+        int n;
+
+        state = new minlmstate();
+        n = ap.len(x);
+        minlm.minlmcreatefgh(n, x, state.innerobj);
+
+        return;
+    }
+
+    /*************************************************************************
+    This function sets stopping conditions for Levenberg-Marquardt optimization
+    algorithm.
+
+    INPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+        EpsG    -   >=0
+                    The  subroutine  finishes  its  work   if   the  condition
+                    |v|<EpsG is satisfied, where:
+                    * |.| means Euclidian norm
+                    * v - scaled gradient vector, v[i]=g[i]*s[i]
+                    * g - gradient
+                    * s - scaling coefficients set by MinLMSetScale()
+        EpsF    -   >=0
+                    The  subroutine  finishes  its work if on k+1-th iteration
+                    the  condition  |F(k+1)-F(k)|<=EpsF*max{|F(k)|,|F(k+1)|,1}
+                    is satisfied.
+        EpsX    -   >=0
+                    The subroutine finishes its work if  on  k+1-th  iteration
+                    the condition |v|<=EpsX is fulfilled, where:
+                    * |.| means Euclidian norm
+                    * v - scaled step vector, v[i]=dx[i]/s[i]
+                    * dx - ste pvector, dx=X(k+1)-X(k)
+                    * s - scaling coefficients set by MinLMSetScale()
+        MaxIts  -   maximum number of iterations. If MaxIts=0, the  number  of
+                    iterations   is    unlimited.   Only   Levenberg-Marquardt
+                    iterations  are  counted  (L-BFGS/CG  iterations  are  NOT
+                    counted because their cost is very low compared to that of
+                    LM).
+
+    Passing EpsG=0, EpsF=0, EpsX=0 and MaxIts=0 (simultaneously) will lead to
+    automatic stopping criterion selection (small EpsX).
+
+      -- ALGLIB --
+         Copyright 02.04.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minlmsetcond(minlmstate state, double epsg, double epsf, double epsx, int maxits)
+    {
+
+        minlm.minlmsetcond(state.innerobj, epsg, epsf, epsx, maxits);
+        return;
+    }
+
+    /*************************************************************************
+    This function turns on/off reporting.
+
+    INPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+        NeedXRep-   whether iteration reports are needed or not
+
+    If NeedXRep is True, algorithm will call rep() callback function if  it is
+    provided to MinLMOptimize(). Both Levenberg-Marquardt and internal  L-BFGS
+    iterations are reported.
+
+      -- ALGLIB --
+         Copyright 02.04.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minlmsetxrep(minlmstate state, bool needxrep)
+    {
+
+        minlm.minlmsetxrep(state.innerobj, needxrep);
+        return;
+    }
+
+    /*************************************************************************
+    This function sets maximum step length
+
+    INPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+        StpMax  -   maximum step length, >=0. Set StpMax to 0.0,  if you don't
+                    want to limit step length.
+
+    Use this subroutine when you optimize target function which contains exp()
+    or  other  fast  growing  functions,  and optimization algorithm makes too
+    large  steps  which  leads  to overflow. This function allows us to reject
+    steps  that  are  too  large  (and  therefore  expose  us  to the possible
+    overflow) without actually calculating function value at the x+stp*d.
+
+    NOTE: non-zero StpMax leads to moderate  performance  degradation  because
+    intermediate  step  of  preconditioned L-BFGS optimization is incompatible
+    with limits on step size.
+
+      -- ALGLIB --
+         Copyright 02.04.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minlmsetstpmax(minlmstate state, double stpmax)
+    {
+
+        minlm.minlmsetstpmax(state.innerobj, stpmax);
+        return;
+    }
+
+    /*************************************************************************
+    This function sets scaling coefficients for LM optimizer.
+
+    ALGLIB optimizers use scaling matrices to test stopping  conditions  (step
+    size and gradient are scaled before comparison with tolerances).  Scale of
+    the I-th variable is a translation invariant measure of:
+    a) "how large" the variable is
+    b) how large the step should be to make significant changes in the function
+
+    Generally, scale is NOT considered to be a form of preconditioner.  But LM
+    optimizer is unique in that it uses scaling matrix both  in  the  stopping
+    condition tests and as Marquardt damping factor.
+
+    Proper scaling is very important for the algorithm performance. It is less
+    important for the quality of results, but still has some influence (it  is
+    easier  to  converge  when  variables  are  properly  scaled, so premature
+    stopping is possible when very badly scalled variables are  combined  with
+    relaxed stopping conditions).
+
+    INPUT PARAMETERS:
+        State   -   structure stores algorithm state
+        S       -   array[N], non-zero scaling coefficients
+                    S[i] may be negative, sign doesn't matter.
+
+      -- ALGLIB --
+         Copyright 14.01.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minlmsetscale(minlmstate state, double[] s)
+    {
+
+        minlm.minlmsetscale(state.innerobj, s);
+        return;
+    }
+
+    /*************************************************************************
+    This function sets boundary constraints for LM optimizer
+
+    Boundary constraints are inactive by default (after initial creation).
+    They are preserved until explicitly turned off with another SetBC() call.
+
+    INPUT PARAMETERS:
+        State   -   structure stores algorithm state
+        BndL    -   lower bounds, array[N].
+                    If some (all) variables are unbounded, you may specify
+                    very small number or -INF (latter is recommended because
+                    it will allow solver to use better algorithm).
+        BndU    -   upper bounds, array[N].
+                    If some (all) variables are unbounded, you may specify
+                    very large number or +INF (latter is recommended because
+                    it will allow solver to use better algorithm).
+
+    NOTE 1: it is possible to specify BndL[i]=BndU[i]. In this case I-th
+    variable will be "frozen" at X[i]=BndL[i]=BndU[i].
+
+    NOTE 2: this solver has following useful properties:
+    * bound constraints are always satisfied exactly
+    * function is evaluated only INSIDE area specified by bound constraints
+      or at its boundary
+
+      -- ALGLIB --
+         Copyright 14.01.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minlmsetbc(minlmstate state, double[] bndl, double[] bndu)
+    {
+
+        minlm.minlmsetbc(state.innerobj, bndl, bndu);
+        return;
+    }
+
+    /*************************************************************************
+    This function is used to change acceleration settings
+
+    You can choose between three acceleration strategies:
+    * AccType=0, no acceleration.
+    * AccType=1, secant updates are used to update quadratic model after  each
+      iteration. After fixed number of iterations (or after  model  breakdown)
+      we  recalculate  quadratic  model  using  analytic  Jacobian  or  finite
+      differences. Number of secant-based iterations depends  on  optimization
+      settings: about 3 iterations - when we have analytic Jacobian, up to 2*N
+      iterations - when we use finite differences to calculate Jacobian.
+
+    AccType=1 is recommended when Jacobian  calculation  cost  is  prohibitive
+    high (several Mx1 function vector calculations  followed  by  several  NxN
+    Cholesky factorizations are faster than calculation of one M*N  Jacobian).
+    It should also be used when we have no Jacobian, because finite difference
+    approximation takes too much time to compute.
+
+    Table below list  optimization  protocols  (XYZ  protocol  corresponds  to
+    MinLMCreateXYZ) and acceleration types they support (and use by  default).
+
+    ACCELERATION TYPES SUPPORTED BY OPTIMIZATION PROTOCOLS:
+
+    protocol    0   1   comment
+    V           +   +
+    VJ          +   +
+    FGH         +
+
+    DAFAULT VALUES:
+
+    protocol    0   1   comment
+    V               x   without acceleration it is so slooooooooow
+    VJ          x
+    FGH         x
+
+    NOTE: this  function should be called before optimization. Attempt to call
+    it during algorithm iterations may result in unexpected behavior.
+
+    NOTE: attempt to call this function with unsupported protocol/acceleration
+    combination will result in exception being thrown.
+
+      -- ALGLIB --
+         Copyright 14.10.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minlmsetacctype(minlmstate state, int acctype)
+    {
+
+        minlm.minlmsetacctype(state.innerobj, acctype);
+        return;
+    }
+
+    /*************************************************************************
+    This function provides reverse communication interface
+    Reverse communication interface is not documented or recommended to use.
+    See below for functions which provide better documented API
+    *************************************************************************/
+    public static bool minlmiteration(minlmstate state)
+    {
+
+        bool result = minlm.minlmiteration(state.innerobj);
+        return result;
+    }
+    /*************************************************************************
+    This family of functions is used to launcn iterations of nonlinear optimizer
+
+    These functions accept following parameters:
+        func    -   callback which calculates function (or merit function)
+                    value func at given point x
+        grad    -   callback which calculates function (or merit function)
+                    value func and gradient grad at given point x
+        hess    -   callback which calculates function (or merit function)
+                    value func, gradient grad and Hessian hess at given point x
+        fvec    -   callback which calculates function vector fi[]
+                    at given point x
+        jac     -   callback which calculates function vector fi[]
+                    and Jacobian jac at given point x
+        rep     -   optional callback which is called after each iteration
+                    can be null
+        obj     -   optional object which is passed to func/grad/hess/jac/rep
+                    can be null
+
+    NOTES:
+
+    1. Depending on function used to create state  structure,  this  algorithm
+       may accept Jacobian and/or Hessian and/or gradient.  According  to  the
+       said above, there ase several versions of this function,  which  accept
+       different sets of callbacks.
+
+       This flexibility opens way to subtle errors - you may create state with
+       MinLMCreateFGH() (optimization using Hessian), but call function  which
+       does not accept Hessian. So when algorithm will request Hessian,  there
+       will be no callback to call. In this case exception will be thrown.
+
+       Be careful to avoid such errors because there is no way to find them at
+       compile time - you can see them at runtime only.
+
+      -- ALGLIB --
+         Copyright 10.03.2009 by Bochkanov Sergey
+
+    *************************************************************************/
+    public static void minlmoptimize(minlmstate state, ndimensional_fvec  fvec, ndimensional_rep rep, object obj)
+    {
+        if( fvec==null )
+            throw new alglibexception("ALGLIB: error in 'minlmoptimize()' (fvec is null)");
+        while( alglib.minlmiteration(state) )
+        {
+            if( state.needfi )
+            {
+                fvec(state.x, state.innerobj.fi, obj);
+                continue;
+            }
+            if( state.innerobj.xupdated )
+            {
+                if( rep!=null )
+                    rep(state.innerobj.x, state.innerobj.f, obj);
+                continue;
+            }
+            throw new alglibexception("ALGLIB: error in 'minlmoptimize' (some derivatives were not provided?)");
+        }
+    }
+
+
+    public static void minlmoptimize(minlmstate state, ndimensional_fvec  fvec, ndimensional_jac  jac, ndimensional_rep rep, object obj)
+    {
+        if( fvec==null )
+            throw new alglibexception("ALGLIB: error in 'minlmoptimize()' (fvec is null)");
+        if( jac==null )
+            throw new alglibexception("ALGLIB: error in 'minlmoptimize()' (jac is null)");
+        while( alglib.minlmiteration(state) )
+        {
+            if( state.needfi )
+            {
+                fvec(state.x, state.innerobj.fi, obj);
+                continue;
+            }
+            if( state.needfij )
+            {
+                jac(state.x, state.innerobj.fi, state.innerobj.j, obj);
+                continue;
+            }
+            if( state.innerobj.xupdated )
+            {
+                if( rep!=null )
+                    rep(state.innerobj.x, state.innerobj.f, obj);
+                continue;
+            }
+            throw new alglibexception("ALGLIB: error in 'minlmoptimize' (some derivatives were not provided?)");
+        }
+    }
+
+
+    public static void minlmoptimize(minlmstate state, ndimensional_func func, ndimensional_grad grad, ndimensional_hess hess, ndimensional_rep rep, object obj)
+    {
+        if( func==null )
+            throw new alglibexception("ALGLIB: error in 'minlmoptimize()' (func is null)");
+        if( grad==null )
+            throw new alglibexception("ALGLIB: error in 'minlmoptimize()' (grad is null)");
+        if( hess==null )
+            throw new alglibexception("ALGLIB: error in 'minlmoptimize()' (hess is null)");
+        while( alglib.minlmiteration(state) )
+        {
+            if( state.needf )
+            {
+                func(state.x, ref state.innerobj.f, obj);
+                continue;
+            }
+            if( state.needfg )
+            {
+                grad(state.x, ref state.innerobj.f, state.innerobj.g, obj);
+                continue;
+            }
+            if( state.needfgh )
+            {
+                hess(state.x, ref state.innerobj.f, state.innerobj.g, state.innerobj.h, obj);
+                continue;
+            }
+            if( state.innerobj.xupdated )
+            {
+                if( rep!=null )
+                    rep(state.innerobj.x, state.innerobj.f, obj);
+                continue;
+            }
+            throw new alglibexception("ALGLIB: error in 'minlmoptimize' (some derivatives were not provided?)");
+        }
+    }
+
+
+    public static void minlmoptimize(minlmstate state, ndimensional_func func, ndimensional_jac  jac, ndimensional_rep rep, object obj)
+    {
+        if( func==null )
+            throw new alglibexception("ALGLIB: error in 'minlmoptimize()' (func is null)");
+        if( jac==null )
+            throw new alglibexception("ALGLIB: error in 'minlmoptimize()' (jac is null)");
+        while( alglib.minlmiteration(state) )
+        {
+            if( state.needf )
+            {
+                func(state.x, ref state.innerobj.f, obj);
+                continue;
+            }
+            if( state.needfij )
+            {
+                jac(state.x, state.innerobj.fi, state.innerobj.j, obj);
+                continue;
+            }
+            if( state.innerobj.xupdated )
+            {
+                if( rep!=null )
+                    rep(state.innerobj.x, state.innerobj.f, obj);
+                continue;
+            }
+            throw new alglibexception("ALGLIB: error in 'minlmoptimize' (some derivatives were not provided?)");
+        }
+    }
+
+
+    public static void minlmoptimize(minlmstate state, ndimensional_func func, ndimensional_grad grad, ndimensional_jac  jac, ndimensional_rep rep, object obj)
+    {
+        if( func==null )
+            throw new alglibexception("ALGLIB: error in 'minlmoptimize()' (func is null)");
+        if( grad==null )
+            throw new alglibexception("ALGLIB: error in 'minlmoptimize()' (grad is null)");
+        if( jac==null )
+            throw new alglibexception("ALGLIB: error in 'minlmoptimize()' (jac is null)");
+        while( alglib.minlmiteration(state) )
+        {
+            if( state.needf )
+            {
+                func(state.x, ref state.innerobj.f, obj);
+                continue;
+            }
+            if( state.needfg )
+            {
+                grad(state.x, ref state.innerobj.f, state.innerobj.g, obj);
+                continue;
+            }
+            if( state.needfij )
+            {
+                jac(state.x, state.innerobj.fi, state.innerobj.j, obj);
+                continue;
+            }
+            if( state.innerobj.xupdated )
+            {
+                if( rep!=null )
+                    rep(state.innerobj.x, state.innerobj.f, obj);
+                continue;
+            }
+            throw new alglibexception("ALGLIB: error in 'minlmoptimize' (some derivatives were not provided?)");
+        }
+    }
+
+
+
+    /*************************************************************************
+    Levenberg-Marquardt algorithm results
+
+    INPUT PARAMETERS:
+        State   -   algorithm state
+
+    OUTPUT PARAMETERS:
+        X       -   array[0..N-1], solution
+        Rep     -   optimization report;
+                    see comments for this structure for more info.
+
+      -- ALGLIB --
+         Copyright 10.03.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minlmresults(minlmstate state, out double[] x, out minlmreport rep)
+    {
+        x = new double[0];
+        rep = new minlmreport();
+        minlm.minlmresults(state.innerobj, ref x, rep.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    Levenberg-Marquardt algorithm results
+
+    Buffered implementation of MinLMResults(), which uses pre-allocated buffer
+    to store X[]. If buffer size is  too  small,  it  resizes  buffer.  It  is
+    intended to be used in the inner cycles of performance critical algorithms
+    where array reallocation penalty is too large to be ignored.
+
+      -- ALGLIB --
+         Copyright 10.03.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minlmresultsbuf(minlmstate state, ref double[] x, minlmreport rep)
+    {
+
+        minlm.minlmresultsbuf(state.innerobj, ref x, rep.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    This  subroutine  restarts  LM  algorithm from new point. All optimization
+    parameters are left unchanged.
+
+    This  function  allows  to  solve multiple  optimization  problems  (which
+    must have same number of dimensions) without object reallocation penalty.
+
+    INPUT PARAMETERS:
+        State   -   structure used for reverse communication previously
+                    allocated with MinLMCreateXXX call.
+        X       -   new starting point.
+
+      -- ALGLIB --
+         Copyright 30.07.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minlmrestartfrom(minlmstate state, double[] x)
+    {
+
+        minlm.minlmrestartfrom(state.innerobj, x);
+        return;
+    }
+
+    /*************************************************************************
+    This is obsolete function.
+
+    Since ALGLIB 3.3 it is equivalent to MinLMCreateVJ().
+
+      -- ALGLIB --
+         Copyright 30.03.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minlmcreatevgj(int n, int m, double[] x, out minlmstate state)
+    {
+        state = new minlmstate();
+        minlm.minlmcreatevgj(n, m, x, state.innerobj);
+        return;
+    }
+    public static void minlmcreatevgj(int m, double[] x, out minlmstate state)
+    {
+        int n;
+
+        state = new minlmstate();
+        n = ap.len(x);
+        minlm.minlmcreatevgj(n, m, x, state.innerobj);
+
+        return;
+    }
+
+    /*************************************************************************
+    This is obsolete function.
+
+    Since ALGLIB 3.3 it is equivalent to MinLMCreateFJ().
+
+      -- ALGLIB --
+         Copyright 30.03.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minlmcreatefgj(int n, int m, double[] x, out minlmstate state)
+    {
+        state = new minlmstate();
+        minlm.minlmcreatefgj(n, m, x, state.innerobj);
+        return;
+    }
+    public static void minlmcreatefgj(int m, double[] x, out minlmstate state)
+    {
+        int n;
+
+        state = new minlmstate();
+        n = ap.len(x);
+        minlm.minlmcreatefgj(n, m, x, state.innerobj);
+
+        return;
+    }
+
+    /*************************************************************************
+    This function is considered obsolete since ALGLIB 3.1.0 and is present for
+    backward  compatibility  only.  We  recommend  to use MinLMCreateVJ, which
+    provides similar, but more consistent and feature-rich interface.
+
+      -- ALGLIB --
+         Copyright 30.03.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minlmcreatefj(int n, int m, double[] x, out minlmstate state)
+    {
+        state = new minlmstate();
+        minlm.minlmcreatefj(n, m, x, state.innerobj);
+        return;
+    }
+    public static void minlmcreatefj(int m, double[] x, out minlmstate state)
+    {
+        int n;
+
+        state = new minlmstate();
+        n = ap.len(x);
+        minlm.minlmcreatefj(n, m, x, state.innerobj);
+
+        return;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+
+    *************************************************************************/
+    public class minasastate
+    {
+        //
+        // Public declarations
+        //
+        public bool needfg { get { return _innerobj.needfg; } set { _innerobj.needfg = value; } }
+        public bool xupdated { get { return _innerobj.xupdated; } set { _innerobj.xupdated = value; } }
+        public double f { get { return _innerobj.f; } set { _innerobj.f = value; } }
+        public double[] g { get { return _innerobj.g; } }
+        public double[] x { get { return _innerobj.x; } }
+
+        public minasastate()
+        {
+            _innerobj = new mincomp.minasastate();
+        }
+
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private mincomp.minasastate _innerobj;
+        public mincomp.minasastate innerobj { get { return _innerobj; } }
+        public minasastate(mincomp.minasastate obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+
+    /*************************************************************************
+
+    *************************************************************************/
+    public class minasareport
+    {
+        //
+        // Public declarations
+        //
+        public int iterationscount { get { return _innerobj.iterationscount; } set { _innerobj.iterationscount = value; } }
+        public int nfev { get { return _innerobj.nfev; } set { _innerobj.nfev = value; } }
+        public int terminationtype { get { return _innerobj.terminationtype; } set { _innerobj.terminationtype = value; } }
+        public int activeconstraints { get { return _innerobj.activeconstraints; } set { _innerobj.activeconstraints = value; } }
+
+        public minasareport()
+        {
+            _innerobj = new mincomp.minasareport();
+        }
+
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private mincomp.minasareport _innerobj;
+        public mincomp.minasareport innerobj { get { return _innerobj; } }
+        public minasareport(mincomp.minasareport obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+    /*************************************************************************
+    Obsolete function, use MinLBFGSSetPrecDefault() instead.
+
+      -- ALGLIB --
+         Copyright 13.10.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minlbfgssetdefaultpreconditioner(minlbfgsstate state)
+    {
+
+        mincomp.minlbfgssetdefaultpreconditioner(state.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    Obsolete function, use MinLBFGSSetCholeskyPreconditioner() instead.
+
+      -- ALGLIB --
+         Copyright 13.10.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minlbfgssetcholeskypreconditioner(minlbfgsstate state, double[,] p, bool isupper)
+    {
+
+        mincomp.minlbfgssetcholeskypreconditioner(state.innerobj, p, isupper);
+        return;
+    }
+
+    /*************************************************************************
+    This is obsolete function which was used by previous version of the  BLEIC
+    optimizer. It does nothing in the current version of BLEIC.
+
+      -- ALGLIB --
+         Copyright 28.11.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minbleicsetbarrierwidth(minbleicstate state, double mu)
+    {
+
+        mincomp.minbleicsetbarrierwidth(state.innerobj, mu);
+        return;
+    }
+
+    /*************************************************************************
+    This is obsolete function which was used by previous version of the  BLEIC
+    optimizer. It does nothing in the current version of BLEIC.
+
+      -- ALGLIB --
+         Copyright 28.11.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minbleicsetbarrierdecay(minbleicstate state, double mudecay)
+    {
+
+        mincomp.minbleicsetbarrierdecay(state.innerobj, mudecay);
+        return;
+    }
+
+    /*************************************************************************
+    Obsolete optimization algorithm.
+    Was replaced by MinBLEIC subpackage.
+
+      -- ALGLIB --
+         Copyright 25.03.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minasacreate(int n, double[] x, double[] bndl, double[] bndu, out minasastate state)
+    {
+        state = new minasastate();
+        mincomp.minasacreate(n, x, bndl, bndu, state.innerobj);
+        return;
+    }
+    public static void minasacreate(double[] x, double[] bndl, double[] bndu, out minasastate state)
+    {
+        int n;
+        if( (ap.len(x)!=ap.len(bndl)) || (ap.len(x)!=ap.len(bndu)))
+            throw new alglibexception("Error while calling 'minasacreate': looks like one of arguments has wrong size");
+        state = new minasastate();
+        n = ap.len(x);
+        mincomp.minasacreate(n, x, bndl, bndu, state.innerobj);
+
+        return;
+    }
+
+    /*************************************************************************
+    Obsolete optimization algorithm.
+    Was replaced by MinBLEIC subpackage.
+
+      -- ALGLIB --
+         Copyright 02.04.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minasasetcond(minasastate state, double epsg, double epsf, double epsx, int maxits)
+    {
+
+        mincomp.minasasetcond(state.innerobj, epsg, epsf, epsx, maxits);
+        return;
+    }
+
+    /*************************************************************************
+    Obsolete optimization algorithm.
+    Was replaced by MinBLEIC subpackage.
+
+      -- ALGLIB --
+         Copyright 02.04.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minasasetxrep(minasastate state, bool needxrep)
+    {
+
+        mincomp.minasasetxrep(state.innerobj, needxrep);
+        return;
+    }
+
+    /*************************************************************************
+    Obsolete optimization algorithm.
+    Was replaced by MinBLEIC subpackage.
+
+      -- ALGLIB --
+         Copyright 02.04.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minasasetalgorithm(minasastate state, int algotype)
+    {
+
+        mincomp.minasasetalgorithm(state.innerobj, algotype);
+        return;
+    }
+
+    /*************************************************************************
+    Obsolete optimization algorithm.
+    Was replaced by MinBLEIC subpackage.
+
+      -- ALGLIB --
+         Copyright 02.04.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minasasetstpmax(minasastate state, double stpmax)
+    {
+
+        mincomp.minasasetstpmax(state.innerobj, stpmax);
+        return;
+    }
+
+    /*************************************************************************
+    This function provides reverse communication interface
+    Reverse communication interface is not documented or recommended to use.
+    See below for functions which provide better documented API
+    *************************************************************************/
+    public static bool minasaiteration(minasastate state)
+    {
+
+        bool result = mincomp.minasaiteration(state.innerobj);
+        return result;
+    }
+    /*************************************************************************
+    This family of functions is used to launcn iterations of nonlinear optimizer
+
+    These functions accept following parameters:
+        grad    -   callback which calculates function (or merit function)
+                    value func and gradient grad at given point x
+        rep     -   optional callback which is called after each iteration
+                    can be null
+        obj     -   optional object which is passed to func/grad/hess/jac/rep
+                    can be null
+
+
+      -- ALGLIB --
+         Copyright 20.03.2009 by Bochkanov Sergey
+
+    *************************************************************************/
+    public static void minasaoptimize(minasastate state, ndimensional_grad grad, ndimensional_rep rep, object obj)
+    {
+        if( grad==null )
+            throw new alglibexception("ALGLIB: error in 'minasaoptimize()' (grad is null)");
+        while( alglib.minasaiteration(state) )
+        {
+            if( state.needfg )
+            {
+                grad(state.x, ref state.innerobj.f, state.innerobj.g, obj);
+                continue;
+            }
+            if( state.innerobj.xupdated )
+            {
+                if( rep!=null )
+                    rep(state.innerobj.x, state.innerobj.f, obj);
+                continue;
+            }
+            throw new alglibexception("ALGLIB: error in 'minasaoptimize' (some derivatives were not provided?)");
+        }
+    }
+
+
+
+    /*************************************************************************
+    Obsolete optimization algorithm.
+    Was replaced by MinBLEIC subpackage.
+
+      -- ALGLIB --
+         Copyright 20.03.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minasaresults(minasastate state, out double[] x, out minasareport rep)
+    {
+        x = new double[0];
+        rep = new minasareport();
+        mincomp.minasaresults(state.innerobj, ref x, rep.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    Obsolete optimization algorithm.
+    Was replaced by MinBLEIC subpackage.
+
+      -- ALGLIB --
+         Copyright 20.03.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minasaresultsbuf(minasastate state, ref double[] x, minasareport rep)
+    {
+
+        mincomp.minasaresultsbuf(state.innerobj, ref x, rep.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    Obsolete optimization algorithm.
+    Was replaced by MinBLEIC subpackage.
+
+      -- ALGLIB --
+         Copyright 30.07.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minasarestartfrom(minasastate state, double[] x, double[] bndl, double[] bndu)
+    {
+
+        mincomp.minasarestartfrom(state.innerobj, x, bndl, bndu);
+        return;
+    }
+
+}
+public partial class alglib
+{
+    public class optserv
+    {
+        /*************************************************************************
+        This subroutine is used to prepare threshold value which will be used for
+        trimming of the target function (see comments on TrimFunction() for more
+        information).
+
+        This function accepts only one parameter: function value at the starting
+        point. It returns threshold which will be used for trimming.
+
+          -- ALGLIB --
+             Copyright 10.05.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void trimprepare(double f,
+            ref double threshold)
+        {
+            threshold = 0;
+
+            threshold = 10*(Math.Abs(f)+1);
+        }
+
+
+        /*************************************************************************
+        This subroutine is used to "trim" target function, i.e. to do following
+        transformation:
+
+                           { {F,G}          if F<Threshold
+            {F_tr, G_tr} = {
+                           { {Threshold, 0} if F>=Threshold
+                           
+        Such transformation allows us to  solve  problems  with  singularities  by
+        redefining function in such way that it becomes bounded from above.
+
+          -- ALGLIB --
+             Copyright 10.05.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void trimfunction(ref double f,
+            ref double[] g,
+            int n,
+            double threshold)
+        {
+            int i = 0;
+
+            if( (double)(f)>=(double)(threshold) )
+            {
+                f = threshold;
+                for(i=0; i<=n-1; i++)
+                {
+                    g[i] = 0.0;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This function enforces boundary constraints in the X.
+
+        This function correctly (although a bit inefficient) handles BL[i] which
+        are -INF and BU[i] which are +INF.
+
+        We have NMain+NSlack  dimensional  X,  with first NMain components bounded
+        by BL/BU, and next NSlack ones bounded by non-negativity constraints.
+
+        INPUT PARAMETERS
+            X       -   array[NMain+NSlack], point
+            BL      -   array[NMain], lower bounds
+                        (may contain -INF, when bound is not present)
+            HaveBL  -   array[NMain], if HaveBL[i] is False,
+                        then i-th bound is not present
+            BU      -   array[NMain], upper bounds
+                        (may contain +INF, when bound is not present)
+            HaveBU  -   array[NMain], if HaveBU[i] is False,
+                        then i-th bound is not present
+
+        OUTPUT PARAMETERS
+            X       -   X with all constraints being enforced
+
+        It returns True when constraints are consistent,
+        False - when constraints are inconsistent.
+
+          -- ALGLIB --
+             Copyright 10.01.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static bool enforceboundaryconstraints(ref double[] x,
+            double[] bl,
+            bool[] havebl,
+            double[] bu,
+            bool[] havebu,
+            int nmain,
+            int nslack)
+        {
+            bool result = new bool();
+            int i = 0;
+
+            result = false;
+            for(i=0; i<=nmain-1; i++)
+            {
+                if( (havebl[i] && havebu[i]) && (double)(bl[i])>(double)(bu[i]) )
+                {
+                    return result;
+                }
+                if( havebl[i] && (double)(x[i])<(double)(bl[i]) )
+                {
+                    x[i] = bl[i];
+                }
+                if( havebu[i] && (double)(x[i])>(double)(bu[i]) )
+                {
+                    x[i] = bu[i];
+                }
+            }
+            for(i=0; i<=nslack-1; i++)
+            {
+                if( (double)(x[nmain+i])<(double)(0) )
+                {
+                    x[nmain+i] = 0;
+                }
+            }
+            result = true;
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function projects gradient into feasible area of boundary constrained
+        optimization  problem.  X  can  be  infeasible  with  respect  to boundary
+        constraints.  We  have  NMain+NSlack  dimensional  X,   with  first  NMain 
+        components bounded by BL/BU, and next NSlack ones bounded by non-negativity
+        constraints.
+
+        INPUT PARAMETERS
+            X       -   array[NMain+NSlack], point
+            G       -   array[NMain+NSlack], gradient
+            BL      -   lower bounds (may contain -INF, when bound is not present)
+            HaveBL  -   if HaveBL[i] is False, then i-th bound is not present
+            BU      -   upper bounds (may contain +INF, when bound is not present)
+            HaveBU  -   if HaveBU[i] is False, then i-th bound is not present
+
+        OUTPUT PARAMETERS
+            G       -   projection of G. Components of G which satisfy one of the
+                        following
+                            (1) (X[I]<=BndL[I]) and (G[I]>0), OR
+                            (2) (X[I]>=BndU[I]) and (G[I]<0)
+                        are replaced by zeros.
+
+        NOTE 1: this function assumes that constraints are feasible. It throws
+        exception otherwise.
+
+        NOTE 2: in fact, projection of ANTI-gradient is calculated,  because  this
+        function trims components of -G which points outside of the feasible area.
+        However, working with -G is considered confusing, because all optimization
+        source work with G.
+
+          -- ALGLIB --
+             Copyright 10.01.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void projectgradientintobc(double[] x,
+            ref double[] g,
+            double[] bl,
+            bool[] havebl,
+            double[] bu,
+            bool[] havebu,
+            int nmain,
+            int nslack)
+        {
+            int i = 0;
+
+            for(i=0; i<=nmain-1; i++)
+            {
+                alglib.ap.assert((!havebl[i] || !havebu[i]) || (double)(bl[i])<=(double)(bu[i]), "ProjectGradientIntoBC: internal error (infeasible constraints)");
+                if( (havebl[i] && (double)(x[i])<=(double)(bl[i])) && (double)(g[i])>(double)(0) )
+                {
+                    g[i] = 0;
+                }
+                if( (havebu[i] && (double)(x[i])>=(double)(bu[i])) && (double)(g[i])<(double)(0) )
+                {
+                    g[i] = 0;
+                }
+            }
+            for(i=0; i<=nslack-1; i++)
+            {
+                if( (double)(x[nmain+i])<=(double)(0) && (double)(g[nmain+i])>(double)(0) )
+                {
+                    g[nmain+i] = 0;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Given
+            a) initial point X0[NMain+NSlack]
+               (feasible with respect to bound constraints)
+            b) step vector alpha*D[NMain+NSlack]
+            c) boundary constraints BndL[NMain], BndU[NMain]
+            d) implicit non-negativity constraints for slack variables
+        this  function  calculates  bound  on  the step length subject to boundary
+        constraints.
+
+        It returns:
+            *  MaxStepLen - such step length that X0+MaxStepLen*alpha*D is exactly
+               at the boundary given by constraints
+            *  VariableToFreeze - index of the constraint to be activated,
+               0 <= VariableToFreeze < NMain+NSlack
+            *  ValueToFreeze - value of the corresponding constraint.
+
+        Notes:
+            * it is possible that several constraints can be activated by the step
+              at once. In such cases only one constraint is returned. It is caller
+              responsibility to check other constraints. This function makes  sure
+              that we activate at least one constraint, and everything else is the
+              responsibility of the caller.
+            * steps smaller than MaxStepLen still can activate constraints due  to
+              numerical errors. Thus purpose of this  function  is  not  to  guard 
+              against accidental activation of the constraints - quite the reverse, 
+              its purpose is to activate at least constraint upon performing  step
+              which is too long.
+            * in case there is no constraints to activate, we return negative
+              VariableToFreeze and zero MaxStepLen and ValueToFreeze.
+            * this function assumes that constraints are consistent; it throws
+              exception otherwise.
+
+        INPUT PARAMETERS
+            X           -   array[NMain+NSlack], point. Must be feasible with respect 
+                            to bound constraints (exception will be thrown otherwise)
+            D           -   array[NMain+NSlack], step direction
+            alpha       -   scalar multiplier before D, alpha<>0
+            BndL        -   lower bounds, array[NMain]
+                            (may contain -INF, when bound is not present)
+            HaveBndL    -   array[NMain], if HaveBndL[i] is False,
+                            then i-th bound is not present
+            BndU        -   array[NMain], upper bounds
+                            (may contain +INF, when bound is not present)
+            HaveBndU    -   array[NMain], if HaveBndU[i] is False,
+                            then i-th bound is not present
+            NMain       -   number of main variables
+            NSlack      -   number of slack variables
+            
+        OUTPUT PARAMETERS
+            VariableToFreeze:
+                            * negative value     = step is unbounded, ValueToFreeze=0,
+                                                   MaxStepLen=0.
+                            * non-negative value = at least one constraint, given by
+                                                   this parameter, will  be  activated
+                                                   upon performing maximum step.
+            ValueToFreeze-  value of the variable which will be constrained
+            MaxStepLen  -   maximum length of the step. Can be zero when step vector
+                            looks outside of the feasible area.
+
+          -- ALGLIB --
+             Copyright 10.01.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void calculatestepbound(double[] x,
+            double[] d,
+            double alpha,
+            double[] bndl,
+            bool[] havebndl,
+            double[] bndu,
+            bool[] havebndu,
+            int nmain,
+            int nslack,
+            ref int variabletofreeze,
+            ref double valuetofreeze,
+            ref double maxsteplen)
+        {
+            int i = 0;
+            double prevmax = 0;
+            double initval = 0;
+
+            variabletofreeze = 0;
+            valuetofreeze = 0;
+            maxsteplen = 0;
+
+            alglib.ap.assert((double)(alpha)!=(double)(0), "CalculateStepBound: zero alpha");
+            variabletofreeze = -1;
+            initval = math.maxrealnumber;
+            maxsteplen = initval;
+            for(i=0; i<=nmain-1; i++)
+            {
+                if( havebndl[i] && (double)(alpha*d[i])<(double)(0) )
+                {
+                    alglib.ap.assert((double)(x[i])>=(double)(bndl[i]), "CalculateStepBound: infeasible X");
+                    prevmax = maxsteplen;
+                    maxsteplen = apserv.safeminposrv(x[i]-bndl[i], -(alpha*d[i]), maxsteplen);
+                    if( (double)(maxsteplen)<(double)(prevmax) )
+                    {
+                        variabletofreeze = i;
+                        valuetofreeze = bndl[i];
+                    }
+                }
+                if( havebndu[i] && (double)(alpha*d[i])>(double)(0) )
+                {
+                    alglib.ap.assert((double)(x[i])<=(double)(bndu[i]), "CalculateStepBound: infeasible X");
+                    prevmax = maxsteplen;
+                    maxsteplen = apserv.safeminposrv(bndu[i]-x[i], alpha*d[i], maxsteplen);
+                    if( (double)(maxsteplen)<(double)(prevmax) )
+                    {
+                        variabletofreeze = i;
+                        valuetofreeze = bndu[i];
+                    }
+                }
+            }
+            for(i=0; i<=nslack-1; i++)
+            {
+                if( (double)(alpha*d[nmain+i])<(double)(0) )
+                {
+                    alglib.ap.assert((double)(x[nmain+i])>=(double)(0), "CalculateStepBound: infeasible X");
+                    prevmax = maxsteplen;
+                    maxsteplen = apserv.safeminposrv(x[nmain+i], -(alpha*d[nmain+i]), maxsteplen);
+                    if( (double)(maxsteplen)<(double)(prevmax) )
+                    {
+                        variabletofreeze = nmain+i;
+                        valuetofreeze = 0;
+                    }
+                }
+            }
+            if( (double)(maxsteplen)==(double)(initval) )
+            {
+                valuetofreeze = 0;
+                maxsteplen = 0;
+            }
+        }
+
+
+        /*************************************************************************
+        This function postprocesses bounded step by:
+        * analysing step length (whether it is equal to MaxStepLen) and activating 
+          constraint given by VariableToFreeze if needed
+        * checking for additional bound constraints to activate
+
+        This function uses final point of the step, quantities calculated  by  the
+        CalculateStepBound()  function.  As  result,  it  returns  point  which is 
+        exactly feasible with respect to boundary constraints.
+
+        NOTE 1: this function does NOT handle and check linear equality constraints
+        NOTE 2: when StepTaken=MaxStepLen we always activate at least one constraint
+
+        INPUT PARAMETERS
+            X           -   array[NMain+NSlack], final point to postprocess
+            XPrev       -   array[NMain+NSlack], initial point
+            BndL        -   lower bounds, array[NMain]
+                            (may contain -INF, when bound is not present)
+            HaveBndL    -   array[NMain], if HaveBndL[i] is False,
+                            then i-th bound is not present
+            BndU        -   array[NMain], upper bounds
+                            (may contain +INF, when bound is not present)
+            HaveBndU    -   array[NMain], if HaveBndU[i] is False,
+                            then i-th bound is not present
+            NMain       -   number of main variables
+            NSlack      -   number of slack variables
+            VariableToFreeze-result of CalculateStepBound()
+            ValueToFreeze-  result of CalculateStepBound()
+            StepTaken   -   actual step length (actual step is equal to the possibly 
+                            non-unit step direction vector times this parameter).
+                            StepTaken<=MaxStepLen.
+            MaxStepLen  -   result of CalculateStepBound()
+            
+        OUTPUT PARAMETERS
+            X           -   point bounded with respect to constraints.
+                            components corresponding to active constraints are exactly
+                            equal to the boundary values.
+                            
+        RESULT:
+            number of constraints activated in addition to previously active ones.
+            Constraints which were DEACTIVATED are ignored (do not influence
+            function value).
+
+          -- ALGLIB --
+             Copyright 10.01.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static int postprocessboundedstep(ref double[] x,
+            double[] xprev,
+            double[] bndl,
+            bool[] havebndl,
+            double[] bndu,
+            bool[] havebndu,
+            int nmain,
+            int nslack,
+            int variabletofreeze,
+            double valuetofreeze,
+            double steptaken,
+            double maxsteplen)
+        {
+            int result = 0;
+            int i = 0;
+
+            alglib.ap.assert(variabletofreeze<0 || (double)(steptaken)<=(double)(maxsteplen));
+            
+            //
+            // Activate constraints
+            //
+            if( variabletofreeze>=0 && (double)(steptaken)==(double)(maxsteplen) )
+            {
+                x[variabletofreeze] = valuetofreeze;
+            }
+            for(i=0; i<=nmain-1; i++)
+            {
+                if( havebndl[i] && (double)(x[i])<(double)(bndl[i]) )
+                {
+                    x[i] = bndl[i];
+                }
+                if( havebndu[i] && (double)(x[i])>(double)(bndu[i]) )
+                {
+                    x[i] = bndu[i];
+                }
+            }
+            for(i=0; i<=nslack-1; i++)
+            {
+                if( (double)(x[nmain+i])<=(double)(0) )
+                {
+                    x[nmain+i] = 0;
+                }
+            }
+            
+            //
+            // Calculate number of constraints being activated
+            //
+            result = 0;
+            for(i=0; i<=nmain-1; i++)
+            {
+                if( (double)(x[i])!=(double)(xprev[i]) && ((havebndl[i] && (double)(x[i])==(double)(bndl[i])) || (havebndu[i] && (double)(x[i])==(double)(bndu[i]))) )
+                {
+                    result = result+1;
+                }
+            }
+            for(i=0; i<=nslack-1; i++)
+            {
+                if( (double)(x[nmain+i])!=(double)(xprev[nmain+i]) && (double)(x[nmain+i])==(double)(0.0) )
+                {
+                    result = result+1;
+                }
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        The  purpose  of  this  function is to prevent algorithm from "unsticking" 
+        from  the  active  bound  constraints  because  of  numerical noise in the
+        gradient or Hessian.
+
+        It is done by zeroing some components of the search direction D.  D[i]  is
+        zeroed when both (a) and (b) are true:
+        a) corresponding X[i] is exactly at the boundary
+        b) |D[i]*S[i]| <= DropTol*Sqrt(SUM(D[i]^2*S[I]^2))
+
+        D  can  be  step  direction , antigradient, gradient, or anything similar. 
+        Sign of D does not matter, nor matters step length.
+
+        NOTE 1: boundary constraints are expected to be consistent, as well as X
+                is expected to be feasible. Exception will be thrown otherwise.
+
+        INPUT PARAMETERS
+            D           -   array[NMain+NSlack], direction
+            X           -   array[NMain+NSlack], current point
+            BndL        -   lower bounds, array[NMain]
+                            (may contain -INF, when bound is not present)
+            HaveBndL    -   array[NMain], if HaveBndL[i] is False,
+                            then i-th bound is not present
+            BndU        -   array[NMain], upper bounds
+                            (may contain +INF, when bound is not present)
+            HaveBndU    -   array[NMain], if HaveBndU[i] is False,
+                            then i-th bound is not present
+            S           -   array[NMain+NSlack], scaling of the variables
+            NMain       -   number of main variables
+            NSlack      -   number of slack variables
+            DropTol     -   drop tolerance, >=0
+            
+        OUTPUT PARAMETERS
+            X           -   point bounded with respect to constraints.
+                            components corresponding to active constraints are exactly
+                            equal to the boundary values.
+
+          -- ALGLIB --
+             Copyright 10.01.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void filterdirection(ref double[] d,
+            double[] x,
+            double[] bndl,
+            bool[] havebndl,
+            double[] bndu,
+            bool[] havebndu,
+            double[] s,
+            int nmain,
+            int nslack,
+            double droptol)
+        {
+            int i = 0;
+            double scalednorm = 0;
+            bool isactive = new bool();
+
+            scalednorm = 0.0;
+            for(i=0; i<=nmain+nslack-1; i++)
+            {
+                scalednorm = scalednorm+math.sqr(d[i]*s[i]);
+            }
+            scalednorm = Math.Sqrt(scalednorm);
+            for(i=0; i<=nmain-1; i++)
+            {
+                alglib.ap.assert(!havebndl[i] || (double)(x[i])>=(double)(bndl[i]), "FilterDirection: infeasible point");
+                alglib.ap.assert(!havebndu[i] || (double)(x[i])<=(double)(bndu[i]), "FilterDirection: infeasible point");
+                isactive = (havebndl[i] && (double)(x[i])==(double)(bndl[i])) || (havebndu[i] && (double)(x[i])==(double)(bndu[i]));
+                if( isactive && (double)(Math.Abs(d[i]*s[i]))<=(double)(droptol*scalednorm) )
+                {
+                    d[i] = 0.0;
+                }
+            }
+            for(i=0; i<=nslack-1; i++)
+            {
+                alglib.ap.assert((double)(x[nmain+i])>=(double)(0), "FilterDirection: infeasible point");
+                if( (double)(x[nmain+i])==(double)(0) && (double)(Math.Abs(d[nmain+i]*s[nmain+i]))<=(double)(droptol*scalednorm) )
+                {
+                    d[nmain+i] = 0.0;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This function returns number of bound constraints whose state was  changed
+        (either activated or deactivated) when making step from XPrev to X.
+
+        Constraints are considered:
+        * active - when we are exactly at the boundary
+        * inactive - when we are not at the boundary
+
+        You should note that antigradient direction is NOT taken into account when
+        we make decions on the constraint status.
+
+        INPUT PARAMETERS
+            X           -   array[NMain+NSlack], final point.
+                            Must be feasible with respect to bound constraints.
+            XPrev       -   array[NMain+NSlack], initial point.
+                            Must be feasible with respect to bound constraints.
+            BndL        -   lower bounds, array[NMain]
+                            (may contain -INF, when bound is not present)
+            HaveBndL    -   array[NMain], if HaveBndL[i] is False,
+                            then i-th bound is not present
+            BndU        -   array[NMain], upper bounds
+                            (may contain +INF, when bound is not present)
+            HaveBndU    -   array[NMain], if HaveBndU[i] is False,
+                            then i-th bound is not present
+            NMain       -   number of main variables
+            NSlack      -   number of slack variables
+            
+        RESULT:
+            number of constraints whose state was changed.
+
+          -- ALGLIB --
+             Copyright 10.01.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static int numberofchangedconstraints(double[] x,
+            double[] xprev,
+            double[] bndl,
+            bool[] havebndl,
+            double[] bndu,
+            bool[] havebndu,
+            int nmain,
+            int nslack)
+        {
+            int result = 0;
+            int i = 0;
+            bool statuschanged = new bool();
+
+            result = 0;
+            for(i=0; i<=nmain-1; i++)
+            {
+                if( (double)(x[i])!=(double)(xprev[i]) )
+                {
+                    statuschanged = false;
+                    if( havebndl[i] && ((double)(x[i])==(double)(bndl[i]) || (double)(xprev[i])==(double)(bndl[i])) )
+                    {
+                        statuschanged = true;
+                    }
+                    if( havebndu[i] && ((double)(x[i])==(double)(bndu[i]) || (double)(xprev[i])==(double)(bndu[i])) )
+                    {
+                        statuschanged = true;
+                    }
+                    if( statuschanged )
+                    {
+                        result = result+1;
+                    }
+                }
+            }
+            for(i=0; i<=nslack-1; i++)
+            {
+                if( (double)(x[nmain+i])!=(double)(xprev[nmain+i]) && ((double)(x[nmain+i])==(double)(0) || (double)(xprev[nmain+i])==(double)(0)) )
+                {
+                    result = result+1;
+                }
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function finds feasible point of  (NMain+NSlack)-dimensional  problem
+        subject to NMain explicit boundary constraints (some  constraints  can  be
+        omitted), NSlack implicit non-negativity constraints,  K  linear  equality
+        constraints.
+
+        INPUT PARAMETERS
+            X           -   array[NMain+NSlack], initial point.
+            BndL        -   lower bounds, array[NMain]
+                            (may contain -INF, when bound is not present)
+            HaveBndL    -   array[NMain], if HaveBndL[i] is False,
+                            then i-th bound is not present
+            BndU        -   array[NMain], upper bounds
+                            (may contain +INF, when bound is not present)
+            HaveBndU    -   array[NMain], if HaveBndU[i] is False,
+                            then i-th bound is not present
+            NMain       -   number of main variables
+            NSlack      -   number of slack variables
+            CE          -   array[K,NMain+NSlack+1], equality  constraints CE*x=b.
+                            Rows contain constraints, first  NMain+NSlack  columns
+                            contain coefficients before X[], last  column  contain
+                            right part.
+            K           -   number of linear constraints
+            EpsI        -   infeasibility (error in the right part) allowed in the
+                            solution
+
+        OUTPUT PARAMETERS:
+            X           -   feasible point or best infeasible point found before
+                            algorithm termination
+            QPIts       -   number of QP iterations (for debug purposes)
+            GPAIts      -   number of GPA iterations (for debug purposes)
+            
+        RESULT:
+            True in case X is feasible, False - if it is infeasible.
+
+          -- ALGLIB --
+             Copyright 20.01.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static bool findfeasiblepoint(ref double[] x,
+            double[] bndl,
+            bool[] havebndl,
+            double[] bndu,
+            bool[] havebndu,
+            int nmain,
+            int nslack,
+            double[,] ce,
+            int k,
+            double epsi,
+            ref int qpits,
+            ref int gpaits)
+        {
+            bool result = new bool();
+            int i = 0;
+            int j = 0;
+            int idx0 = 0;
+            int idx1 = 0;
+            double[] permx = new double[0];
+            double[] xn = new double[0];
+            double[] xa = new double[0];
+            double[] newtonstep = new double[0];
+            double[] g = new double[0];
+            double[] pg = new double[0];
+            double[,] a = new double[0,0];
+            double armijostep = 0;
+            double armijobeststep = 0;
+            double armijobestfeas = 0;
+            double v = 0;
+            double mx = 0;
+            double feaserr = 0;
+            double feasold = 0;
+            double feasnew = 0;
+            double pgnorm = 0;
+            double vn = 0;
+            double vd = 0;
+            double stp = 0;
+            int vartofreeze = 0;
+            double valtofreeze = 0;
+            double maxsteplen = 0;
+            bool werechangesinconstraints = new bool();
+            bool stage1isover = new bool();
+            bool converged = new bool();
+            double[] activeconstraints = new double[0];
+            double[] tmpk = new double[0];
+            double[] colnorms = new double[0];
+            int nactive = 0;
+            int nfree = 0;
+            int nsvd = 0;
+            int[] p1 = new int[0];
+            int[] p2 = new int[0];
+            apserv.apbuffers buf = new apserv.apbuffers();
+            double[] w = new double[0];
+            double[] s = new double[0];
+            double[,] u = new double[0,0];
+            double[,] vt = new double[0,0];
+            int itscount = 0;
+            int itswithintolerance = 0;
+            int maxitswithintolerance = 0;
+            int gparuns = 0;
+            int maxgparuns = 0;
+            int maxarmijoruns = 0;
+            int i_ = 0;
+
+            ce = (double[,])ce.Clone();
+            qpits = 0;
+            gpaits = 0;
+
+            maxitswithintolerance = 3;
+            maxgparuns = 3;
+            maxarmijoruns = 5;
+            qpits = 0;
+            gpaits = 0;
+            
+            //
+            // Initial enforcement of the feasibility with respect to boundary constraints
+            // NOTE: after this block we assume that boundary constraints are consistent.
+            //
+            if( !enforceboundaryconstraints(ref x, bndl, havebndl, bndu, havebndu, nmain, nslack) )
+            {
+                result = false;
+                return result;
+            }
+            if( k==0 )
+            {
+                
+                //
+                // No linear constraints, we can exit right now
+                //
+                result = true;
+                return result;
+            }
+            
+            //
+            // Scale rows of CE in such way that max(CE[i,0..nmain+nslack-1])=1 for any i=0..k-1
+            //
+            for(i=0; i<=k-1; i++)
+            {
+                v = 0.0;
+                for(j=0; j<=nmain+nslack-1; j++)
+                {
+                    v = Math.Max(v, Math.Abs(ce[i,j]));
+                }
+                if( (double)(v)!=(double)(0) )
+                {
+                    v = 1/v;
+                    for(i_=0; i_<=nmain+nslack;i_++)
+                    {
+                        ce[i,i_] = v*ce[i,i_];
+                    }
+                }
+            }
+            
+            //
+            // Allocate temporaries
+            //
+            xn = new double[nmain+nslack];
+            xa = new double[nmain+nslack];
+            permx = new double[nmain+nslack];
+            g = new double[nmain+nslack];
+            pg = new double[nmain+nslack];
+            tmpk = new double[k];
+            a = new double[k, nmain+nslack];
+            activeconstraints = new double[nmain+nslack];
+            newtonstep = new double[nmain+nslack];
+            s = new double[nmain+nslack];
+            colnorms = new double[nmain+nslack];
+            for(i=0; i<=nmain+nslack-1; i++)
+            {
+                s[i] = 1.0;
+                colnorms[i] = 0.0;
+                for(j=0; j<=k-1; j++)
+                {
+                    colnorms[i] = colnorms[i]+math.sqr(ce[j,i]);
+                }
+            }
+            
+            //
+            // K>0, we have linear equality constraints combined with bound constraints.
+            //
+            // Try to find feasible point as minimizer of the quadratic function
+            //     F(x) = 0.5*||CE*x-b||^2 = 0.5*x'*(CE'*CE)*x - (b'*CE)*x + 0.5*b'*b
+            // subject to boundary constraints given by BL, BU and non-negativity of
+            // the slack variables. BTW, we drop constant term because it does not
+            // actually influences on the solution.
+            //
+            // Below we will assume that K>0.
+            //
+            itswithintolerance = 0;
+            itscount = 0;
+            while( true )
+            {
+                
+                //
+                // Stage 0: check for exact convergence
+                //
+                converged = true;
+                feaserr = 0;
+                for(i=0; i<=k-1; i++)
+                {
+                    
+                    //
+                    // Calculate:
+                    // * V - error in the right part
+                    // * MX - maximum term in the left part
+                    //
+                    // Terminate if error in the right part is not greater than 100*Eps*MX.
+                    //
+                    // IMPORTANT: we must perform check for non-strict inequality, i.e. to use <= instead of <.
+                    //            it will allow us to easily handle situations with zero rows of CE.
+                    //
+                    mx = 0;
+                    v = -ce[i,nmain+nslack];
+                    for(j=0; j<=nmain+nslack-1; j++)
+                    {
+                        mx = Math.Max(mx, Math.Abs(ce[i,j]*x[j]));
+                        v = v+ce[i,j]*x[j];
+                    }
+                    feaserr = feaserr+math.sqr(v);
+                    converged = converged && (double)(Math.Abs(v))<=(double)(100*math.machineepsilon*mx);
+                }
+                feaserr = Math.Sqrt(feaserr);
+                if( converged )
+                {
+                    result = (double)(feaserr)<=(double)(epsi);
+                    return result;
+                }
+                
+                //
+                // Stage 1: equality constrained quadratic programming
+                //
+                // * treat active bound constraints as equality ones (constraint is considered 
+                //   active when we are at the boundary, independently of the antigradient direction)
+                // * calculate unrestricted Newton step to point XM (which may be infeasible)
+                //   calculate MaxStepLen = largest step in direction of XM which retains feasibility.
+                // * perform bounded step from X to XN:
+                //   a) XN=XM                  (if XM is feasible)
+                //   b) XN=X-MaxStepLen*(XM-X) (otherwise)
+                // * X := XN
+                // * if XM (Newton step subject to currently active constraints) was feasible, goto Stage 2
+                // * repeat Stage 1
+                //
+                // NOTE 1: in order to solve constrained qudratic subproblem we will have to reorder
+                //         variables in such way that ones corresponding to inactive constraints will
+                //         be first, and active ones will be last in the list. CE and X are now
+                //                                                       [ xi ]
+                //         separated into two parts: CE = [CEi CEa], x = [    ], where CEi/Xi correspond
+                //                                                       [ xa ]
+                //         to INACTIVE constraints, and CEa/Xa correspond to the ACTIVE ones.
+                //
+                //         Now, instead of F=0.5*x'*(CE'*CE)*x - (b'*CE)*x + 0.5*b'*b, we have
+                //         F(xi) = 0.5*(CEi*xi,CEi*xi) + (CEa*xa-b,CEi*xi) + (0.5*CEa*xa-b,CEa*xa).
+                //         Here xa is considered constant, i.e. we optimize with respect to xi, leaving xa fixed.
+                //
+                //         We can solve it by performing SVD of CEi and calculating pseudoinverse of the
+                //         Hessian matrix. Of course, we do NOT calculate pseudoinverse explicitly - we
+                //         just use singular vectors to perform implicit multiplication by it.
+                //
+                //
+                while( true )
+                {
+                    
+                    //
+                    // Calculate G - gradient subject to equality constraints,
+                    // multiply it by inverse of the Hessian diagonal to obtain initial
+                    // step vector.
+                    //
+                    // Bound step subject to constraints which can be activated,
+                    // run Armijo search with increasing step size.
+                    // Search is terminated when feasibility error stops to decrease.
+                    //
+                    // NOTE: it is important to test for "stops to decrease" instead
+                    // of "starts to increase" in order to correctly handle cases with
+                    // zero CE.
+                    //
+                    armijobeststep = 0.0;
+                    armijobestfeas = 0.0;
+                    for(i=0; i<=nmain+nslack-1; i++)
+                    {
+                        g[i] = 0;
+                    }
+                    for(i=0; i<=k-1; i++)
+                    {
+                        v = 0.0;
+                        for(i_=0; i_<=nmain+nslack-1;i_++)
+                        {
+                            v += ce[i,i_]*x[i_];
+                        }
+                        v = v-ce[i,nmain+nslack];
+                        armijobestfeas = armijobestfeas+math.sqr(v);
+                        for(i_=0; i_<=nmain+nslack-1;i_++)
+                        {
+                            g[i_] = g[i_] + v*ce[i,i_];
+                        }
+                    }
+                    armijobestfeas = Math.Sqrt(armijobestfeas);
+                    for(i=0; i<=nmain-1; i++)
+                    {
+                        if( havebndl[i] && (double)(x[i])==(double)(bndl[i]) )
+                        {
+                            g[i] = 0.0;
+                        }
+                        if( havebndu[i] && (double)(x[i])==(double)(bndu[i]) )
+                        {
+                            g[i] = 0.0;
+                        }
+                    }
+                    for(i=0; i<=nslack-1; i++)
+                    {
+                        if( (double)(x[nmain+i])==(double)(0.0) )
+                        {
+                            g[nmain+i] = 0.0;
+                        }
+                    }
+                    v = 0.0;
+                    for(i=0; i<=nmain+nslack-1; i++)
+                    {
+                        if( (double)(math.sqr(colnorms[i]))!=(double)(0) )
+                        {
+                            newtonstep[i] = -(g[i]/math.sqr(colnorms[i]));
+                        }
+                        else
+                        {
+                            newtonstep[i] = 0.0;
+                        }
+                        v = v+math.sqr(newtonstep[i]);
+                    }
+                    if( (double)(v)==(double)(0) )
+                    {
+                        
+                        //
+                        // Constrained gradient is zero, QP iterations are over
+                        //
+                        break;
+                    }
+                    calculatestepbound(x, newtonstep, 1.0, bndl, havebndl, bndu, havebndu, nmain, nslack, ref vartofreeze, ref valtofreeze, ref maxsteplen);
+                    if( vartofreeze>=0 && (double)(maxsteplen)==(double)(0) )
+                    {
+                        
+                        //
+                        // Can not perform step, QP iterations are over
+                        //
+                        break;
+                    }
+                    if( vartofreeze>=0 )
+                    {
+                        armijostep = Math.Min(1.0, maxsteplen);
+                    }
+                    else
+                    {
+                        armijostep = 1;
+                    }
+                    while( true )
+                    {
+                        for(i_=0; i_<=nmain+nslack-1;i_++)
+                        {
+                            xa[i_] = x[i_];
+                        }
+                        for(i_=0; i_<=nmain+nslack-1;i_++)
+                        {
+                            xa[i_] = xa[i_] + armijostep*newtonstep[i_];
+                        }
+                        enforceboundaryconstraints(ref xa, bndl, havebndl, bndu, havebndu, nmain, nslack);
+                        feaserr = 0.0;
+                        for(i=0; i<=k-1; i++)
+                        {
+                            v = 0.0;
+                            for(i_=0; i_<=nmain+nslack-1;i_++)
+                            {
+                                v += ce[i,i_]*xa[i_];
+                            }
+                            v = v-ce[i,nmain+nslack];
+                            feaserr = feaserr+math.sqr(v);
+                        }
+                        feaserr = Math.Sqrt(feaserr);
+                        if( (double)(feaserr)>=(double)(armijobestfeas) )
+                        {
+                            break;
+                        }
+                        armijobestfeas = feaserr;
+                        armijobeststep = armijostep;
+                        armijostep = 2.0*armijostep;
+                    }
+                    for(i_=0; i_<=nmain+nslack-1;i_++)
+                    {
+                        x[i_] = x[i_] + armijobeststep*newtonstep[i_];
+                    }
+                    enforceboundaryconstraints(ref x, bndl, havebndl, bndu, havebndu, nmain, nslack);
+                    
+                    //
+                    // Determine number of active and free constraints
+                    //
+                    nactive = 0;
+                    for(i=0; i<=nmain-1; i++)
+                    {
+                        activeconstraints[i] = 0;
+                        if( havebndl[i] && (double)(x[i])==(double)(bndl[i]) )
+                        {
+                            activeconstraints[i] = 1;
+                        }
+                        if( havebndu[i] && (double)(x[i])==(double)(bndu[i]) )
+                        {
+                            activeconstraints[i] = 1;
+                        }
+                        if( (double)(activeconstraints[i])>(double)(0) )
+                        {
+                            nactive = nactive+1;
+                        }
+                    }
+                    for(i=0; i<=nslack-1; i++)
+                    {
+                        activeconstraints[nmain+i] = 0;
+                        if( (double)(x[nmain+i])==(double)(0.0) )
+                        {
+                            activeconstraints[nmain+i] = 1;
+                        }
+                        if( (double)(activeconstraints[nmain+i])>(double)(0) )
+                        {
+                            nactive = nactive+1;
+                        }
+                    }
+                    nfree = nmain+nslack-nactive;
+                    if( nfree==0 )
+                    {
+                        break;
+                    }
+                    qpits = qpits+1;
+                    
+                    //
+                    // Reorder variables
+                    //
+                    tsort.tagsortbuf(ref activeconstraints, nmain+nslack, ref p1, ref p2, buf);
+                    for(i=0; i<=k-1; i++)
+                    {
+                        for(j=0; j<=nmain+nslack-1; j++)
+                        {
+                            a[i,j] = ce[i,j];
+                        }
+                    }
+                    for(j=0; j<=nmain+nslack-1; j++)
+                    {
+                        permx[j] = x[j];
+                    }
+                    for(j=0; j<=nmain+nslack-1; j++)
+                    {
+                        if( p2[j]!=j )
+                        {
+                            idx0 = p2[j];
+                            idx1 = j;
+                            for(i=0; i<=k-1; i++)
+                            {
+                                v = a[i,idx0];
+                                a[i,idx0] = a[i,idx1];
+                                a[i,idx1] = v;
+                            }
+                            v = permx[idx0];
+                            permx[idx0] = permx[idx1];
+                            permx[idx1] = v;
+                        }
+                    }
+                    
+                    //
+                    // Calculate (unprojected) gradient:
+                    // G(xi) = CEi'*(CEi*xi + CEa*xa - b)
+                    //
+                    for(i=0; i<=nfree-1; i++)
+                    {
+                        g[i] = 0;
+                    }
+                    for(i=0; i<=k-1; i++)
+                    {
+                        v = 0.0;
+                        for(i_=0; i_<=nmain+nslack-1;i_++)
+                        {
+                            v += a[i,i_]*permx[i_];
+                        }
+                        tmpk[i] = v-ce[i,nmain+nslack];
+                    }
+                    for(i=0; i<=k-1; i++)
+                    {
+                        v = tmpk[i];
+                        for(i_=0; i_<=nfree-1;i_++)
+                        {
+                            g[i_] = g[i_] + v*a[i,i_];
+                        }
+                    }
+                    
+                    //
+                    // Calculate Newton step using SVD of CEi:
+                    //     F(xi)  = 0.5*xi'*H*xi + g'*xi    (Taylor decomposition)
+                    //     XN     = -H^(-1)*g               (new point, solution of the QP subproblem)
+                    //     H      = CEi'*CEi                
+                    //     CEi    = U*W*V'                  (SVD of CEi)
+                    //     H      = V*W^2*V'                 
+                    //     H^(-1) = V*W^(-2)*V'
+                    //     step     = -V*W^(-2)*V'*g          (it is better to perform multiplication from right to left)
+                    //
+                    // NOTE 1: we do NOT need left singular vectors to perform Newton step.
+                    //
+                    nsvd = Math.Min(k, nfree);
+                    if( !svd.rmatrixsvd(a, k, nfree, 0, 1, 2, ref w, ref u, ref vt) )
+                    {
+                        result = false;
+                        return result;
+                    }
+                    for(i=0; i<=nsvd-1; i++)
+                    {
+                        v = 0.0;
+                        for(i_=0; i_<=nfree-1;i_++)
+                        {
+                            v += vt[i,i_]*g[i_];
+                        }
+                        tmpk[i] = v;
+                    }
+                    for(i=0; i<=nsvd-1; i++)
+                    {
+                        
+                        //
+                        // It is important to have strict ">" in order to correctly 
+                        // handle zero singular values.
+                        //
+                        if( (double)(math.sqr(w[i]))>(double)(math.sqr(w[0])*(nmain+nslack)*math.machineepsilon) )
+                        {
+                            tmpk[i] = tmpk[i]/math.sqr(w[i]);
+                        }
+                        else
+                        {
+                            tmpk[i] = 0;
+                        }
+                    }
+                    for(i=0; i<=nmain+nslack-1; i++)
+                    {
+                        newtonstep[i] = 0;
+                    }
+                    for(i=0; i<=nsvd-1; i++)
+                    {
+                        v = tmpk[i];
+                        for(i_=0; i_<=nfree-1;i_++)
+                        {
+                            newtonstep[i_] = newtonstep[i_] - v*vt[i,i_];
+                        }
+                    }
+                    for(j=nmain+nslack-1; j>=0; j--)
+                    {
+                        if( p2[j]!=j )
+                        {
+                            idx0 = p2[j];
+                            idx1 = j;
+                            v = newtonstep[idx0];
+                            newtonstep[idx0] = newtonstep[idx1];
+                            newtonstep[idx1] = v;
+                        }
+                    }
+                    
+                    //
+                    // NewtonStep contains Newton step subject to active bound constraints.
+                    //
+                    // Such step leads us to the minimizer of the equality constrained F,
+                    // but such minimizer may be infeasible because some constraints which
+                    // are inactive at the initial point can be violated at the solution.
+                    //
+                    // Thus, we perform optimization in two stages:
+                    // a) perform bounded Newton step, i.e. step in the Newton direction
+                    //    until activation of the first constraint
+                    // b) in case (MaxStepLen>0)and(MaxStepLen<1), perform additional iteration
+                    //    of the Armijo line search in the rest of the Newton direction.
+                    //
+                    calculatestepbound(x, newtonstep, 1.0, bndl, havebndl, bndu, havebndu, nmain, nslack, ref vartofreeze, ref valtofreeze, ref maxsteplen);
+                    if( vartofreeze>=0 && (double)(maxsteplen)==(double)(0) )
+                    {
+                        
+                        //
+                        // Activation of the constraints prevent us from performing step,
+                        // QP iterations are over
+                        //
+                        break;
+                    }
+                    if( vartofreeze>=0 )
+                    {
+                        v = Math.Min(1.0, maxsteplen);
+                    }
+                    else
+                    {
+                        v = 1.0;
+                    }
+                    for(i_=0; i_<=nmain+nslack-1;i_++)
+                    {
+                        xn[i_] = v*newtonstep[i_];
+                    }
+                    for(i_=0; i_<=nmain+nslack-1;i_++)
+                    {
+                        xn[i_] = xn[i_] + x[i_];
+                    }
+                    postprocessboundedstep(ref xn, x, bndl, havebndl, bndu, havebndu, nmain, nslack, vartofreeze, valtofreeze, v, maxsteplen);
+                    if( (double)(maxsteplen)>(double)(0) && (double)(maxsteplen)<(double)(1) )
+                    {
+                        
+                        //
+                        // Newton step was restricted by activation of the constraints,
+                        // perform Armijo iteration.
+                        //
+                        // Initial estimate for best step is zero step. We try different
+                        // step sizes, from the 1-MaxStepLen (residual of the full Newton
+                        // step) to progressively smaller and smaller steps.
+                        //
+                        armijobeststep = 0.0;
+                        armijobestfeas = 0.0;
+                        for(i=0; i<=k-1; i++)
+                        {
+                            v = 0.0;
+                            for(i_=0; i_<=nmain+nslack-1;i_++)
+                            {
+                                v += ce[i,i_]*xn[i_];
+                            }
+                            v = v-ce[i,nmain+nslack];
+                            armijobestfeas = armijobestfeas+math.sqr(v);
+                        }
+                        armijobestfeas = Math.Sqrt(armijobestfeas);
+                        armijostep = 1-maxsteplen;
+                        for(j=0; j<=maxarmijoruns-1; j++)
+                        {
+                            for(i_=0; i_<=nmain+nslack-1;i_++)
+                            {
+                                xa[i_] = xn[i_];
+                            }
+                            for(i_=0; i_<=nmain+nslack-1;i_++)
+                            {
+                                xa[i_] = xa[i_] + armijostep*newtonstep[i_];
+                            }
+                            enforceboundaryconstraints(ref xa, bndl, havebndl, bndu, havebndu, nmain, nslack);
+                            feaserr = 0.0;
+                            for(i=0; i<=k-1; i++)
+                            {
+                                v = 0.0;
+                                for(i_=0; i_<=nmain+nslack-1;i_++)
+                                {
+                                    v += ce[i,i_]*xa[i_];
+                                }
+                                v = v-ce[i,nmain+nslack];
+                                feaserr = feaserr+math.sqr(v);
+                            }
+                            feaserr = Math.Sqrt(feaserr);
+                            if( (double)(feaserr)<(double)(armijobestfeas) )
+                            {
+                                armijobestfeas = feaserr;
+                                armijobeststep = armijostep;
+                            }
+                            armijostep = 0.5*armijostep;
+                        }
+                        for(i_=0; i_<=nmain+nslack-1;i_++)
+                        {
+                            xa[i_] = xn[i_];
+                        }
+                        for(i_=0; i_<=nmain+nslack-1;i_++)
+                        {
+                            xa[i_] = xa[i_] + armijobeststep*newtonstep[i_];
+                        }
+                        enforceboundaryconstraints(ref xa, bndl, havebndl, bndu, havebndu, nmain, nslack);
+                    }
+                    else
+                    {
+                        
+                        //
+                        // Armijo iteration is not performed
+                        //
+                        for(i_=0; i_<=nmain+nslack-1;i_++)
+                        {
+                            xa[i_] = xn[i_];
+                        }
+                    }
+                    stage1isover = (double)(maxsteplen)>=(double)(1) || (double)(maxsteplen)==(double)(0);
+                    
+                    //
+                    // Calculate feasibility errors for old and new X.
+                    // These quantinies are used for debugging purposes only.
+                    // However, we can leave them in release code because performance impact is insignificant.
+                    //
+                    // Update X. Exit if needed.
+                    //
+                    feasold = 0;
+                    feasnew = 0;
+                    for(i=0; i<=k-1; i++)
+                    {
+                        v = 0.0;
+                        for(i_=0; i_<=nmain+nslack-1;i_++)
+                        {
+                            v += ce[i,i_]*x[i_];
+                        }
+                        feasold = feasold+math.sqr(v-ce[i,nmain+nslack]);
+                        v = 0.0;
+                        for(i_=0; i_<=nmain+nslack-1;i_++)
+                        {
+                            v += ce[i,i_]*xa[i_];
+                        }
+                        feasnew = feasnew+math.sqr(v-ce[i,nmain+nslack]);
+                    }
+                    feasold = Math.Sqrt(feasold);
+                    feasnew = Math.Sqrt(feasnew);
+                    if( (double)(feasnew)>=(double)(feasold) )
+                    {
+                        break;
+                    }
+                    for(i_=0; i_<=nmain+nslack-1;i_++)
+                    {
+                        x[i_] = xa[i_];
+                    }
+                    if( stage1isover )
+                    {
+                        break;
+                    }
+                }
+                
+                //
+                // Stage 2: gradient projection algorithm (GPA)
+                //
+                // * calculate feasibility error (with respect to linear equality constraints)
+                // * calculate gradient G of F, project it into feasible area (G => PG)
+                // * exit if norm(PG) is exactly zero or feasibility error is smaller than EpsC
+                // * let XM be exact minimum of F along -PG (XM may be infeasible).
+                //   calculate MaxStepLen = largest step in direction of -PG which retains feasibility.
+                // * perform bounded step from X to XN:
+                //   a) XN=XM              (if XM is feasible)
+                //   b) XN=X-MaxStepLen*PG (otherwise)
+                // * X := XN
+                // * stop after specified number of iterations or when no new constraints was activated
+                //
+                // NOTES:
+                // * grad(F) = (CE'*CE)*x - (b'*CE)^T
+                // * CE[i] denotes I-th row of CE
+                // * XM = X+stp*(-PG) where stp=(grad(F(X)),PG)/(CE*PG,CE*PG).
+                //   Here PG is a projected gradient, but in fact it can be arbitrary non-zero 
+                //   direction vector - formula for minimum of F along PG still will be correct.
+                //
+                werechangesinconstraints = false;
+                for(gparuns=1; gparuns<=k; gparuns++)
+                {
+                    
+                    //
+                    // calculate feasibility error and G
+                    //
+                    feaserr = 0;
+                    for(i=0; i<=nmain+nslack-1; i++)
+                    {
+                        g[i] = 0;
+                    }
+                    for(i=0; i<=k-1; i++)
+                    {
+                        
+                        //
+                        // G += CE[i]^T * (CE[i]*x-b[i])
+                        //
+                        v = 0.0;
+                        for(i_=0; i_<=nmain+nslack-1;i_++)
+                        {
+                            v += ce[i,i_]*x[i_];
+                        }
+                        v = v-ce[i,nmain+nslack];
+                        feaserr = feaserr+math.sqr(v);
+                        for(i_=0; i_<=nmain+nslack-1;i_++)
+                        {
+                            g[i_] = g[i_] + v*ce[i,i_];
+                        }
+                    }
+                    
+                    //
+                    // project G, filter it (strip numerical noise)
+                    //
+                    for(i_=0; i_<=nmain+nslack-1;i_++)
+                    {
+                        pg[i_] = g[i_];
+                    }
+                    projectgradientintobc(x, ref pg, bndl, havebndl, bndu, havebndu, nmain, nslack);
+                    filterdirection(ref pg, x, bndl, havebndl, bndu, havebndu, s, nmain, nslack, 1.0E-9);
+                    for(i=0; i<=nmain+nslack-1; i++)
+                    {
+                        if( (double)(math.sqr(colnorms[i]))!=(double)(0) )
+                        {
+                            pg[i] = pg[i]/math.sqr(colnorms[i]);
+                        }
+                        else
+                        {
+                            pg[i] = 0.0;
+                        }
+                    }
+                    
+                    //
+                    // Check GNorm and feasibility.
+                    // Exit when GNorm is exactly zero.
+                    //
+                    pgnorm = 0.0;
+                    for(i_=0; i_<=nmain+nslack-1;i_++)
+                    {
+                        pgnorm += pg[i_]*pg[i_];
+                    }
+                    feaserr = Math.Sqrt(feaserr);
+                    pgnorm = Math.Sqrt(pgnorm);
+                    if( (double)(pgnorm)==(double)(0) )
+                    {
+                        result = (double)(feaserr)<=(double)(epsi);
+                        return result;
+                    }
+                    
+                    //
+                    // calculate planned step length
+                    //
+                    vn = 0.0;
+                    for(i_=0; i_<=nmain+nslack-1;i_++)
+                    {
+                        vn += g[i_]*pg[i_];
+                    }
+                    vd = 0;
+                    for(i=0; i<=k-1; i++)
+                    {
+                        v = 0.0;
+                        for(i_=0; i_<=nmain+nslack-1;i_++)
+                        {
+                            v += ce[i,i_]*pg[i_];
+                        }
+                        vd = vd+math.sqr(v);
+                    }
+                    stp = vn/vd;
+                    
+                    //
+                    // Calculate step bound.
+                    // Perform bounded step and post-process it
+                    //
+                    calculatestepbound(x, pg, -1.0, bndl, havebndl, bndu, havebndu, nmain, nslack, ref vartofreeze, ref valtofreeze, ref maxsteplen);
+                    if( vartofreeze>=0 && (double)(maxsteplen)==(double)(0) )
+                    {
+                        result = false;
+                        return result;
+                    }
+                    if( vartofreeze>=0 )
+                    {
+                        v = Math.Min(stp, maxsteplen);
+                    }
+                    else
+                    {
+                        v = stp;
+                    }
+                    for(i_=0; i_<=nmain+nslack-1;i_++)
+                    {
+                        xn[i_] = x[i_];
+                    }
+                    for(i_=0; i_<=nmain+nslack-1;i_++)
+                    {
+                        xn[i_] = xn[i_] - v*pg[i_];
+                    }
+                    postprocessboundedstep(ref xn, x, bndl, havebndl, bndu, havebndu, nmain, nslack, vartofreeze, valtofreeze, v, maxsteplen);
+                    
+                    //
+                    // update X
+                    // check stopping criteria
+                    //
+                    werechangesinconstraints = werechangesinconstraints || numberofchangedconstraints(xn, x, bndl, havebndl, bndu, havebndu, nmain, nslack)>0;
+                    for(i_=0; i_<=nmain+nslack-1;i_++)
+                    {
+                        x[i_] = xn[i_];
+                    }
+                    gpaits = gpaits+1;
+                    if( !werechangesinconstraints )
+                    {
+                        break;
+                    }
+                }
+                
+                //
+                // Stage 3: decide to stop algorithm or not to stop
+                //
+                // 1. we can stop when last GPA run did NOT changed constraints status.
+                //    It means that we've found final set of the active constraints even
+                //    before GPA made its run. And it means that Newton step moved us to
+                //    the minimum subject to the present constraints.
+                //    Depending on feasibility error, True or False is returned.
+                //
+                feaserr = 0;
+                for(i=0; i<=k-1; i++)
+                {
+                    v = 0.0;
+                    for(i_=0; i_<=nmain+nslack-1;i_++)
+                    {
+                        v += ce[i,i_]*x[i_];
+                    }
+                    v = v-ce[i,nmain+nslack];
+                    feaserr = feaserr+math.sqr(v);
+                }
+                feaserr = Math.Sqrt(feaserr);
+                if( (double)(feaserr)<=(double)(epsi) )
+                {
+                    itswithintolerance = itswithintolerance+1;
+                }
+                else
+                {
+                    itswithintolerance = 0;
+                }
+                if( !werechangesinconstraints || itswithintolerance>=maxitswithintolerance )
+                {
+                    result = (double)(feaserr)<=(double)(epsi);
+                    return result;
+                }
+                itscount = itscount+1;
+            }
+            return result;
+        }
+
+
+    }
+    public class mincg
+    {
+        /*************************************************************************
+        This object stores state of the nonlinear CG optimizer.
+
+        You should use ALGLIB functions to work with this object.
+        *************************************************************************/
+        public class mincgstate
+        {
+            public int n;
+            public double epsg;
+            public double epsf;
+            public double epsx;
+            public int maxits;
+            public double stpmax;
+            public double suggestedstep;
+            public bool xrep;
+            public bool drep;
+            public int cgtype;
+            public int prectype;
+            public double[] diagh;
+            public double[] diaghl2;
+            public double[,] vcorr;
+            public int vcnt;
+            public double[] s;
+            public double diffstep;
+            public int nfev;
+            public int mcstage;
+            public int k;
+            public double[] xk;
+            public double[] dk;
+            public double[] xn;
+            public double[] dn;
+            public double[] d;
+            public double fold;
+            public double stp;
+            public double curstpmax;
+            public double[] yk;
+            public double lastgoodstep;
+            public double lastscaledstep;
+            public int mcinfo;
+            public bool innerresetneeded;
+            public bool terminationneeded;
+            public double trimthreshold;
+            public int rstimer;
+            public double[] x;
+            public double f;
+            public double[] g;
+            public bool needf;
+            public bool needfg;
+            public bool xupdated;
+            public bool algpowerup;
+            public bool lsstart;
+            public bool lsend;
+            public rcommstate rstate;
+            public int repiterationscount;
+            public int repnfev;
+            public int repterminationtype;
+            public int debugrestartscount;
+            public linmin.linminstate lstate;
+            public double fbase;
+            public double fm2;
+            public double fm1;
+            public double fp1;
+            public double fp2;
+            public double betahs;
+            public double betady;
+            public double[] work0;
+            public double[] work1;
+            public mincgstate()
+            {
+                diagh = new double[0];
+                diaghl2 = new double[0];
+                vcorr = new double[0,0];
+                s = new double[0];
+                xk = new double[0];
+                dk = new double[0];
+                xn = new double[0];
+                dn = new double[0];
+                d = new double[0];
+                yk = new double[0];
+                x = new double[0];
+                g = new double[0];
+                rstate = new rcommstate();
+                lstate = new linmin.linminstate();
+                work0 = new double[0];
+                work1 = new double[0];
+            }
+        };
+
+
+        public class mincgreport
+        {
+            public int iterationscount;
+            public int nfev;
+            public int terminationtype;
+        };
+
+
+
+
+        public const int rscountdownlen = 10;
+        public const double gtol = 0.3;
+
+
+        /*************************************************************************
+                NONLINEAR CONJUGATE GRADIENT METHOD
+
+        DESCRIPTION:
+        The subroutine minimizes function F(x) of N arguments by using one of  the
+        nonlinear conjugate gradient methods.
+
+        These CG methods are globally convergent (even on non-convex functions) as
+        long as grad(f) is Lipschitz continuous in  a  some  neighborhood  of  the
+        L = { x : f(x)<=f(x0) }.
+
+
+        REQUIREMENTS:
+        Algorithm will request following information during its operation:
+        * function value F and its gradient G (simultaneously) at given point X
+
+
+        USAGE:
+        1. User initializes algorithm state with MinCGCreate() call
+        2. User tunes solver parameters with MinCGSetCond(), MinCGSetStpMax() and
+           other functions
+        3. User calls MinCGOptimize() function which takes algorithm  state   and
+           pointer (delegate, etc.) to callback function which calculates F/G.
+        4. User calls MinCGResults() to get solution
+        5. Optionally, user may call MinCGRestartFrom() to solve another  problem
+           with same N but another starting point and/or another function.
+           MinCGRestartFrom() allows to reuse already initialized structure.
+
+
+        INPUT PARAMETERS:
+            N       -   problem dimension, N>0:
+                        * if given, only leading N elements of X are used
+                        * if not given, automatically determined from size of X
+            X       -   starting point, array[0..N-1].
+
+        OUTPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+
+          -- ALGLIB --
+             Copyright 25.03.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mincgcreate(int n,
+            double[] x,
+            mincgstate state)
+        {
+            alglib.ap.assert(n>=1, "MinCGCreate: N too small!");
+            alglib.ap.assert(alglib.ap.len(x)>=n, "MinCGCreate: Length(X)<N!");
+            alglib.ap.assert(apserv.isfinitevector(x, n), "MinCGCreate: X contains infinite or NaN values!");
+            mincginitinternal(n, 0.0, state);
+            mincgrestartfrom(state, x);
+        }
+
+
+        /*************************************************************************
+        The subroutine is finite difference variant of MinCGCreate(). It uses
+        finite differences in order to differentiate target function.
+
+        Description below contains information which is specific to this function
+        only. We recommend to read comments on MinCGCreate() in order to get more
+        information about creation of CG optimizer.
+
+        INPUT PARAMETERS:
+            N       -   problem dimension, N>0:
+                        * if given, only leading N elements of X are used
+                        * if not given, automatically determined from size of X
+            X       -   starting point, array[0..N-1].
+            DiffStep-   differentiation step, >0
+
+        OUTPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+
+        NOTES:
+        1. algorithm uses 4-point central formula for differentiation.
+        2. differentiation step along I-th axis is equal to DiffStep*S[I] where
+           S[] is scaling vector which can be set by MinCGSetScale() call.
+        3. we recommend you to use moderate values of  differentiation  step.  Too
+           large step will result in too large truncation  errors, while too small
+           step will result in too large numerical  errors.  1.0E-6  can  be  good
+           value to start with.
+        4. Numerical  differentiation  is   very   inefficient  -   one   gradient
+           calculation needs 4*N function evaluations. This function will work for
+           any N - either small (1...10), moderate (10...100) or  large  (100...).
+           However, performance penalty will be too severe for any N's except  for
+           small ones.
+           We should also say that code which relies on numerical  differentiation
+           is  less  robust  and  precise.  L-BFGS  needs  exact  gradient values.
+           Imprecise  gradient may slow down  convergence,  especially  on  highly
+           nonlinear problems.
+           Thus  we  recommend to use this function for fast prototyping on small-
+           dimensional problems only, and to implement analytical gradient as soon
+           as possible.
+
+          -- ALGLIB --
+             Copyright 16.05.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mincgcreatef(int n,
+            double[] x,
+            double diffstep,
+            mincgstate state)
+        {
+            alglib.ap.assert(n>=1, "MinCGCreateF: N too small!");
+            alglib.ap.assert(alglib.ap.len(x)>=n, "MinCGCreateF: Length(X)<N!");
+            alglib.ap.assert(apserv.isfinitevector(x, n), "MinCGCreateF: X contains infinite or NaN values!");
+            alglib.ap.assert(math.isfinite(diffstep), "MinCGCreateF: DiffStep is infinite or NaN!");
+            alglib.ap.assert((double)(diffstep)>(double)(0), "MinCGCreateF: DiffStep is non-positive!");
+            mincginitinternal(n, diffstep, state);
+            mincgrestartfrom(state, x);
+        }
+
+
+        /*************************************************************************
+        This function sets stopping conditions for CG optimization algorithm.
+
+        INPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+            EpsG    -   >=0
+                        The  subroutine  finishes  its  work   if   the  condition
+                        |v|<EpsG is satisfied, where:
+                        * |.| means Euclidian norm
+                        * v - scaled gradient vector, v[i]=g[i]*s[i]
+                        * g - gradient
+                        * s - scaling coefficients set by MinCGSetScale()
+            EpsF    -   >=0
+                        The  subroutine  finishes  its work if on k+1-th iteration
+                        the  condition  |F(k+1)-F(k)|<=EpsF*max{|F(k)|,|F(k+1)|,1}
+                        is satisfied.
+            EpsX    -   >=0
+                        The subroutine finishes its work if  on  k+1-th  iteration
+                        the condition |v|<=EpsX is fulfilled, where:
+                        * |.| means Euclidian norm
+                        * v - scaled step vector, v[i]=dx[i]/s[i]
+                        * dx - ste pvector, dx=X(k+1)-X(k)
+                        * s - scaling coefficients set by MinCGSetScale()
+            MaxIts  -   maximum number of iterations. If MaxIts=0, the  number  of
+                        iterations is unlimited.
+
+        Passing EpsG=0, EpsF=0, EpsX=0 and MaxIts=0 (simultaneously) will lead to
+        automatic stopping criterion selection (small EpsX).
+
+          -- ALGLIB --
+             Copyright 02.04.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mincgsetcond(mincgstate state,
+            double epsg,
+            double epsf,
+            double epsx,
+            int maxits)
+        {
+            alglib.ap.assert(math.isfinite(epsg), "MinCGSetCond: EpsG is not finite number!");
+            alglib.ap.assert((double)(epsg)>=(double)(0), "MinCGSetCond: negative EpsG!");
+            alglib.ap.assert(math.isfinite(epsf), "MinCGSetCond: EpsF is not finite number!");
+            alglib.ap.assert((double)(epsf)>=(double)(0), "MinCGSetCond: negative EpsF!");
+            alglib.ap.assert(math.isfinite(epsx), "MinCGSetCond: EpsX is not finite number!");
+            alglib.ap.assert((double)(epsx)>=(double)(0), "MinCGSetCond: negative EpsX!");
+            alglib.ap.assert(maxits>=0, "MinCGSetCond: negative MaxIts!");
+            if( (((double)(epsg)==(double)(0) && (double)(epsf)==(double)(0)) && (double)(epsx)==(double)(0)) && maxits==0 )
+            {
+                epsx = 1.0E-6;
+            }
+            state.epsg = epsg;
+            state.epsf = epsf;
+            state.epsx = epsx;
+            state.maxits = maxits;
+        }
+
+
+        /*************************************************************************
+        This function sets scaling coefficients for CG optimizer.
+
+        ALGLIB optimizers use scaling matrices to test stopping  conditions  (step
+        size and gradient are scaled before comparison with tolerances).  Scale of
+        the I-th variable is a translation invariant measure of:
+        a) "how large" the variable is
+        b) how large the step should be to make significant changes in the function
+
+        Scaling is also used by finite difference variant of CG optimizer  -  step
+        along I-th axis is equal to DiffStep*S[I].
+
+        In   most   optimizers  (and  in  the  CG  too)  scaling is NOT a form  of
+        preconditioning. It just  affects  stopping  conditions.  You  should  set
+        preconditioner by separate call to one of the MinCGSetPrec...() functions.
+
+        There  is  special  preconditioning  mode, however,  which  uses   scaling
+        coefficients to form diagonal preconditioning matrix. You  can  turn  this
+        mode on, if you want.   But  you should understand that scaling is not the
+        same thing as preconditioning - these are two different, although  related
+        forms of tuning solver.
+
+        INPUT PARAMETERS:
+            State   -   structure stores algorithm state
+            S       -   array[N], non-zero scaling coefficients
+                        S[i] may be negative, sign doesn't matter.
+
+          -- ALGLIB --
+             Copyright 14.01.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mincgsetscale(mincgstate state,
+            double[] s)
+        {
+            int i = 0;
+
+            alglib.ap.assert(alglib.ap.len(s)>=state.n, "MinCGSetScale: Length(S)<N");
+            for(i=0; i<=state.n-1; i++)
+            {
+                alglib.ap.assert(math.isfinite(s[i]), "MinCGSetScale: S contains infinite or NAN elements");
+                alglib.ap.assert((double)(s[i])!=(double)(0), "MinCGSetScale: S contains zero elements");
+                state.s[i] = Math.Abs(s[i]);
+            }
+        }
+
+
+        /*************************************************************************
+        This function turns on/off reporting.
+
+        INPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+            NeedXRep-   whether iteration reports are needed or not
+
+        If NeedXRep is True, algorithm will call rep() callback function if  it is
+        provided to MinCGOptimize().
+
+          -- ALGLIB --
+             Copyright 02.04.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mincgsetxrep(mincgstate state,
+            bool needxrep)
+        {
+            state.xrep = needxrep;
+        }
+
+
+        /*************************************************************************
+        This function turns on/off line search reports.
+        These reports are described in more details in developer-only  comments on
+        MinCGState object.
+
+        INPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+            NeedDRep-   whether line search reports are needed or not
+
+        This function is intended for private use only. Turning it on artificially
+        may cause program failure.
+
+          -- ALGLIB --
+             Copyright 02.04.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mincgsetdrep(mincgstate state,
+            bool needdrep)
+        {
+            state.drep = needdrep;
+        }
+
+
+        /*************************************************************************
+        This function sets CG algorithm.
+
+        INPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+            CGType  -   algorithm type:
+                        * -1    automatic selection of the best algorithm
+                        * 0     DY (Dai and Yuan) algorithm
+                        * 1     Hybrid DY-HS algorithm
+
+          -- ALGLIB --
+             Copyright 02.04.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mincgsetcgtype(mincgstate state,
+            int cgtype)
+        {
+            alglib.ap.assert(cgtype>=-1 && cgtype<=1, "MinCGSetCGType: incorrect CGType!");
+            if( cgtype==-1 )
+            {
+                cgtype = 1;
+            }
+            state.cgtype = cgtype;
+        }
+
+
+        /*************************************************************************
+        This function sets maximum step length
+
+        INPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+            StpMax  -   maximum step length, >=0. Set StpMax to 0.0,  if you don't
+                        want to limit step length.
+
+        Use this subroutine when you optimize target function which contains exp()
+        or  other  fast  growing  functions,  and optimization algorithm makes too
+        large  steps  which  leads  to overflow. This function allows us to reject
+        steps  that  are  too  large  (and  therefore  expose  us  to the possible
+        overflow) without actually calculating function value at the x+stp*d.
+
+          -- ALGLIB --
+             Copyright 02.04.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mincgsetstpmax(mincgstate state,
+            double stpmax)
+        {
+            alglib.ap.assert(math.isfinite(stpmax), "MinCGSetStpMax: StpMax is not finite!");
+            alglib.ap.assert((double)(stpmax)>=(double)(0), "MinCGSetStpMax: StpMax<0!");
+            state.stpmax = stpmax;
+        }
+
+
+        /*************************************************************************
+        This function allows to suggest initial step length to the CG algorithm.
+
+        Suggested  step  length  is used as starting point for the line search. It
+        can be useful when you have  badly  scaled  problem,  i.e.  when  ||grad||
+        (which is used as initial estimate for the first step) is many  orders  of
+        magnitude different from the desired step.
+
+        Line search  may  fail  on  such problems without good estimate of initial
+        step length. Imagine, for example, problem with ||grad||=10^50 and desired
+        step equal to 0.1 Line  search function will use 10^50  as  initial  step,
+        then  it  will  decrease step length by 2 (up to 20 attempts) and will get
+        10^44, which is still too large.
+
+        This function allows us to tell than line search should  be  started  from
+        some moderate step length, like 1.0, so algorithm will be able  to  detect
+        desired step length in a several searches.
+
+        Default behavior (when no step is suggested) is to use preconditioner,  if
+        it is available, to generate initial estimate of step length.
+
+        This function influences only first iteration of algorithm. It  should  be
+        called between MinCGCreate/MinCGRestartFrom() call and MinCGOptimize call.
+        Suggested step is ignored if you have preconditioner.
+
+        INPUT PARAMETERS:
+            State   -   structure used to store algorithm state.
+            Stp     -   initial estimate of the step length.
+                        Can be zero (no estimate).
+
+          -- ALGLIB --
+             Copyright 30.07.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mincgsuggeststep(mincgstate state,
+            double stp)
+        {
+            alglib.ap.assert(math.isfinite(stp), "MinCGSuggestStep: Stp is infinite or NAN");
+            alglib.ap.assert((double)(stp)>=(double)(0), "MinCGSuggestStep: Stp<0");
+            state.suggestedstep = stp;
+        }
+
+
+        /*************************************************************************
+        Modification of the preconditioner: preconditioning is turned off.
+
+        INPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+
+        NOTE:  you  can  change  preconditioner  "on  the  fly",  during algorithm
+        iterations.
+
+          -- ALGLIB --
+             Copyright 13.10.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mincgsetprecdefault(mincgstate state)
+        {
+            state.prectype = 0;
+            state.innerresetneeded = true;
+        }
+
+
+        /*************************************************************************
+        Modification  of  the  preconditioner:  diagonal of approximate Hessian is
+        used.
+
+        INPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+            D       -   diagonal of the approximate Hessian, array[0..N-1],
+                        (if larger, only leading N elements are used).
+
+        NOTE:  you  can  change  preconditioner  "on  the  fly",  during algorithm
+        iterations.
+
+        NOTE 2: D[i] should be positive. Exception will be thrown otherwise.
+
+        NOTE 3: you should pass diagonal of approximate Hessian - NOT ITS INVERSE.
+
+          -- ALGLIB --
+             Copyright 13.10.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mincgsetprecdiag(mincgstate state,
+            double[] d)
+        {
+            int i = 0;
+
+            alglib.ap.assert(alglib.ap.len(d)>=state.n, "MinCGSetPrecDiag: D is too short");
+            for(i=0; i<=state.n-1; i++)
+            {
+                alglib.ap.assert(math.isfinite(d[i]), "MinCGSetPrecDiag: D contains infinite or NAN elements");
+                alglib.ap.assert((double)(d[i])>(double)(0), "MinCGSetPrecDiag: D contains non-positive elements");
+            }
+            mincgsetprecdiagfast(state, d);
+        }
+
+
+        /*************************************************************************
+        Modification of the preconditioner: scale-based diagonal preconditioning.
+
+        This preconditioning mode can be useful when you  don't  have  approximate
+        diagonal of Hessian, but you know that your  variables  are  badly  scaled
+        (for  example,  one  variable is in [1,10], and another in [1000,100000]),
+        and most part of the ill-conditioning comes from different scales of vars.
+
+        In this case simple  scale-based  preconditioner,  with H[i] = 1/(s[i]^2),
+        can greatly improve convergence.
+
+        IMPRTANT: you should set scale of your variables with MinCGSetScale() call
+        (before or after MinCGSetPrecScale() call). Without knowledge of the scale
+        of your variables scale-based preconditioner will be just unit matrix.
+
+        INPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+
+        NOTE:  you  can  change  preconditioner  "on  the  fly",  during algorithm
+        iterations.
+
+          -- ALGLIB --
+             Copyright 13.10.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mincgsetprecscale(mincgstate state)
+        {
+            state.prectype = 3;
+            state.innerresetneeded = true;
+        }
+
+
+        /*************************************************************************
+        NOTES:
+
+        1. This function has two different implementations: one which  uses  exact
+           (analytical) user-supplied  gradient, and one which uses function value
+           only  and  numerically  differentiates  function  in  order  to  obtain
+           gradient.
+           
+           Depending  on  the  specific  function  used to create optimizer object
+           (either MinCGCreate()  for analytical gradient  or  MinCGCreateF()  for
+           numerical differentiation) you should  choose  appropriate  variant  of
+           MinCGOptimize() - one which accepts function AND gradient or one  which
+           accepts function ONLY.
+
+           Be careful to choose variant of MinCGOptimize()  which  corresponds  to
+           your optimization scheme! Table below lists different  combinations  of
+           callback (function/gradient) passed  to  MinCGOptimize()  and  specific
+           function used to create optimizer.
+           
+
+                          |         USER PASSED TO MinCGOptimize()
+           CREATED WITH   |  function only   |  function and gradient
+           ------------------------------------------------------------
+           MinCGCreateF() |     work                FAIL
+           MinCGCreate()  |     FAIL                work
+
+           Here "FAIL" denotes inappropriate combinations  of  optimizer  creation
+           function and MinCGOptimize() version. Attemps to use  such  combination
+           (for  example,  to create optimizer with  MinCGCreateF()  and  to  pass
+           gradient information to MinCGOptimize()) will lead to  exception  being
+           thrown. Either  you  did  not  pass  gradient when it WAS needed or you
+           passed gradient when it was NOT needed.
+
+          -- ALGLIB --
+             Copyright 20.04.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static bool mincgiteration(mincgstate state)
+        {
+            bool result = new bool();
+            int n = 0;
+            int i = 0;
+            double betak = 0;
+            double v = 0;
+            double vv = 0;
+            int i_ = 0;
+
+            
+            //
+            // Reverse communication preparations
+            // I know it looks ugly, but it works the same way
+            // anywhere from C++ to Python.
+            //
+            // This code initializes locals by:
+            // * random values determined during code
+            //   generation - on first subroutine call
+            // * values from previous call - on subsequent calls
+            //
+            if( state.rstate.stage>=0 )
+            {
+                n = state.rstate.ia[0];
+                i = state.rstate.ia[1];
+                betak = state.rstate.ra[0];
+                v = state.rstate.ra[1];
+                vv = state.rstate.ra[2];
+            }
+            else
+            {
+                n = -983;
+                i = -989;
+                betak = -834;
+                v = 900;
+                vv = -287;
+            }
+            if( state.rstate.stage==0 )
+            {
+                goto lbl_0;
+            }
+            if( state.rstate.stage==1 )
+            {
+                goto lbl_1;
+            }
+            if( state.rstate.stage==2 )
+            {
+                goto lbl_2;
+            }
+            if( state.rstate.stage==3 )
+            {
+                goto lbl_3;
+            }
+            if( state.rstate.stage==4 )
+            {
+                goto lbl_4;
+            }
+            if( state.rstate.stage==5 )
+            {
+                goto lbl_5;
+            }
+            if( state.rstate.stage==6 )
+            {
+                goto lbl_6;
+            }
+            if( state.rstate.stage==7 )
+            {
+                goto lbl_7;
+            }
+            if( state.rstate.stage==8 )
+            {
+                goto lbl_8;
+            }
+            if( state.rstate.stage==9 )
+            {
+                goto lbl_9;
+            }
+            if( state.rstate.stage==10 )
+            {
+                goto lbl_10;
+            }
+            if( state.rstate.stage==11 )
+            {
+                goto lbl_11;
+            }
+            if( state.rstate.stage==12 )
+            {
+                goto lbl_12;
+            }
+            if( state.rstate.stage==13 )
+            {
+                goto lbl_13;
+            }
+            if( state.rstate.stage==14 )
+            {
+                goto lbl_14;
+            }
+            if( state.rstate.stage==15 )
+            {
+                goto lbl_15;
+            }
+            if( state.rstate.stage==16 )
+            {
+                goto lbl_16;
+            }
+            
+            //
+            // Routine body
+            //
+            
+            //
+            // Prepare
+            //
+            n = state.n;
+            state.repterminationtype = 0;
+            state.repiterationscount = 0;
+            state.repnfev = 0;
+            state.debugrestartscount = 0;
+            
+            //
+            // Preparations continue:
+            // * set XK
+            // * calculate F/G
+            // * set DK to -G
+            // * powerup algo (it may change preconditioner)
+            // * apply preconditioner to DK
+            // * report update of X
+            // * check stopping conditions for G
+            //
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.xk[i_] = state.x[i_];
+            }
+            state.terminationneeded = false;
+            clearrequestfields(state);
+            if( (double)(state.diffstep)!=(double)(0) )
+            {
+                goto lbl_17;
+            }
+            state.needfg = true;
+            state.rstate.stage = 0;
+            goto lbl_rcomm;
+        lbl_0:
+            state.needfg = false;
+            goto lbl_18;
+        lbl_17:
+            state.needf = true;
+            state.rstate.stage = 1;
+            goto lbl_rcomm;
+        lbl_1:
+            state.fbase = state.f;
+            i = 0;
+        lbl_19:
+            if( i>n-1 )
+            {
+                goto lbl_21;
+            }
+            v = state.x[i];
+            state.x[i] = v-state.diffstep*state.s[i];
+            state.rstate.stage = 2;
+            goto lbl_rcomm;
+        lbl_2:
+            state.fm2 = state.f;
+            state.x[i] = v-0.5*state.diffstep*state.s[i];
+            state.rstate.stage = 3;
+            goto lbl_rcomm;
+        lbl_3:
+            state.fm1 = state.f;
+            state.x[i] = v+0.5*state.diffstep*state.s[i];
+            state.rstate.stage = 4;
+            goto lbl_rcomm;
+        lbl_4:
+            state.fp1 = state.f;
+            state.x[i] = v+state.diffstep*state.s[i];
+            state.rstate.stage = 5;
+            goto lbl_rcomm;
+        lbl_5:
+            state.fp2 = state.f;
+            state.x[i] = v;
+            state.g[i] = (8*(state.fp1-state.fm1)-(state.fp2-state.fm2))/(6*state.diffstep*state.s[i]);
+            i = i+1;
+            goto lbl_19;
+        lbl_21:
+            state.f = state.fbase;
+            state.needf = false;
+        lbl_18:
+            if( !state.drep )
+            {
+                goto lbl_22;
+            }
+            
+            //
+            // Report algorithm powerup (if needed)
+            //
+            clearrequestfields(state);
+            state.algpowerup = true;
+            state.rstate.stage = 6;
+            goto lbl_rcomm;
+        lbl_6:
+            state.algpowerup = false;
+        lbl_22:
+            optserv.trimprepare(state.f, ref state.trimthreshold);
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.dk[i_] = -state.g[i_];
+            }
+            preconditionedmultiply(state, ref state.dk, ref state.work0, ref state.work1);
+            if( !state.xrep )
+            {
+                goto lbl_24;
+            }
+            clearrequestfields(state);
+            state.xupdated = true;
+            state.rstate.stage = 7;
+            goto lbl_rcomm;
+        lbl_7:
+            state.xupdated = false;
+        lbl_24:
+            if( state.terminationneeded )
+            {
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    state.xn[i_] = state.xk[i_];
+                }
+                state.repterminationtype = 8;
+                result = false;
+                return result;
+            }
+            v = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                v = v+math.sqr(state.g[i]*state.s[i]);
+            }
+            if( (double)(Math.Sqrt(v))<=(double)(state.epsg) )
+            {
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    state.xn[i_] = state.xk[i_];
+                }
+                state.repterminationtype = 4;
+                result = false;
+                return result;
+            }
+            state.repnfev = 1;
+            state.k = 0;
+            state.fold = state.f;
+            
+            //
+            // Choose initial step.
+            // Apply preconditioner, if we have something other than default.
+            //
+            if( state.prectype==2 || state.prectype==3 )
+            {
+                
+                //
+                // because we use preconditioner, step length must be equal
+                // to the norm of DK
+                //
+                v = 0.0;
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    v += state.dk[i_]*state.dk[i_];
+                }
+                state.lastgoodstep = Math.Sqrt(v);
+            }
+            else
+            {
+                
+                //
+                // No preconditioner is used, we try to use suggested step
+                //
+                if( (double)(state.suggestedstep)>(double)(0) )
+                {
+                    state.lastgoodstep = state.suggestedstep;
+                }
+                else
+                {
+                    state.lastgoodstep = 1.0;
+                }
+            }
+            
+            //
+            // Main cycle
+            //
+            state.rstimer = rscountdownlen;
+        lbl_26:
+            if( false )
+            {
+                goto lbl_27;
+            }
+            
+            //
+            // * clear reset flag
+            // * clear termination flag
+            // * store G[k] for later calculation of Y[k]
+            // * prepare starting point and direction and step length for line search
+            //
+            state.innerresetneeded = false;
+            state.terminationneeded = false;
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.yk[i_] = -state.g[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.d[i_] = state.dk[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.xk[i_];
+            }
+            state.mcstage = 0;
+            state.stp = 1.0;
+            linmin.linminnormalized(ref state.d, ref state.stp, n);
+            if( (double)(state.lastgoodstep)!=(double)(0) )
+            {
+                state.stp = state.lastgoodstep;
+            }
+            state.curstpmax = state.stpmax;
+            
+            //
+            // Report beginning of line search (if needed)
+            // Terminate algorithm, if user request was detected
+            //
+            if( !state.drep )
+            {
+                goto lbl_28;
+            }
+            clearrequestfields(state);
+            state.lsstart = true;
+            state.rstate.stage = 8;
+            goto lbl_rcomm;
+        lbl_8:
+            state.lsstart = false;
+        lbl_28:
+            if( state.terminationneeded )
+            {
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    state.xn[i_] = state.x[i_];
+                }
+                state.repterminationtype = 8;
+                result = false;
+                return result;
+            }
+            
+            //
+            // Minimization along D
+            //
+            linmin.mcsrch(n, ref state.x, ref state.f, ref state.g, state.d, ref state.stp, state.curstpmax, gtol, ref state.mcinfo, ref state.nfev, ref state.work0, state.lstate, ref state.mcstage);
+        lbl_30:
+            if( state.mcstage==0 )
+            {
+                goto lbl_31;
+            }
+            
+            //
+            // Calculate function/gradient using either
+            // analytical gradient supplied by user
+            // or finite difference approximation.
+            //
+            // "Trim" function in order to handle near-singularity points.
+            //
+            clearrequestfields(state);
+            if( (double)(state.diffstep)!=(double)(0) )
+            {
+                goto lbl_32;
+            }
+            state.needfg = true;
+            state.rstate.stage = 9;
+            goto lbl_rcomm;
+        lbl_9:
+            state.needfg = false;
+            goto lbl_33;
+        lbl_32:
+            state.needf = true;
+            state.rstate.stage = 10;
+            goto lbl_rcomm;
+        lbl_10:
+            state.fbase = state.f;
+            i = 0;
+        lbl_34:
+            if( i>n-1 )
+            {
+                goto lbl_36;
+            }
+            v = state.x[i];
+            state.x[i] = v-state.diffstep*state.s[i];
+            state.rstate.stage = 11;
+            goto lbl_rcomm;
+        lbl_11:
+            state.fm2 = state.f;
+            state.x[i] = v-0.5*state.diffstep*state.s[i];
+            state.rstate.stage = 12;
+            goto lbl_rcomm;
+        lbl_12:
+            state.fm1 = state.f;
+            state.x[i] = v+0.5*state.diffstep*state.s[i];
+            state.rstate.stage = 13;
+            goto lbl_rcomm;
+        lbl_13:
+            state.fp1 = state.f;
+            state.x[i] = v+state.diffstep*state.s[i];
+            state.rstate.stage = 14;
+            goto lbl_rcomm;
+        lbl_14:
+            state.fp2 = state.f;
+            state.x[i] = v;
+            state.g[i] = (8*(state.fp1-state.fm1)-(state.fp2-state.fm2))/(6*state.diffstep*state.s[i]);
+            i = i+1;
+            goto lbl_34;
+        lbl_36:
+            state.f = state.fbase;
+            state.needf = false;
+        lbl_33:
+            optserv.trimfunction(ref state.f, ref state.g, n, state.trimthreshold);
+            
+            //
+            // Call MCSRCH again
+            //
+            linmin.mcsrch(n, ref state.x, ref state.f, ref state.g, state.d, ref state.stp, state.curstpmax, gtol, ref state.mcinfo, ref state.nfev, ref state.work0, state.lstate, ref state.mcstage);
+            goto lbl_30;
+        lbl_31:
+            
+            //
+            // * report end of line search
+            // * store current point to XN
+            // * report iteration
+            // * terminate algorithm if user request was detected
+            //
+            if( !state.drep )
+            {
+                goto lbl_37;
+            }
+            
+            //
+            // Report end of line search (if needed)
+            //
+            clearrequestfields(state);
+            state.lsend = true;
+            state.rstate.stage = 15;
+            goto lbl_rcomm;
+        lbl_15:
+            state.lsend = false;
+        lbl_37:
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.xn[i_] = state.x[i_];
+            }
+            if( !state.xrep )
+            {
+                goto lbl_39;
+            }
+            clearrequestfields(state);
+            state.xupdated = true;
+            state.rstate.stage = 16;
+            goto lbl_rcomm;
+        lbl_16:
+            state.xupdated = false;
+        lbl_39:
+            if( state.terminationneeded )
+            {
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    state.xn[i_] = state.x[i_];
+                }
+                state.repterminationtype = 8;
+                result = false;
+                return result;
+            }
+            
+            //
+            // Line search is finished.
+            // * calculate BetaK
+            // * calculate DN
+            // * update timers
+            // * calculate step length:
+            //   * LastScaledStep is ALWAYS calculated because it is used in the stopping criteria
+            //   * LastGoodStep is updated only when MCINFO is equal to 1 (Wolfe conditions hold).
+            //     See below for more explanation.
+            //
+            if( state.mcinfo==1 && !state.innerresetneeded )
+            {
+                
+                //
+                // Standard Wolfe conditions hold
+                // Calculate Y[K] and D[K]'*Y[K]
+                //
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    state.yk[i_] = state.yk[i_] + state.g[i_];
+                }
+                vv = 0.0;
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    vv += state.yk[i_]*state.dk[i_];
+                }
+                
+                //
+                // Calculate BetaK according to DY formula
+                //
+                v = preconditionedmultiply2(state, ref state.g, ref state.g, ref state.work0, ref state.work1);
+                state.betady = v/vv;
+                
+                //
+                // Calculate BetaK according to HS formula
+                //
+                v = preconditionedmultiply2(state, ref state.g, ref state.yk, ref state.work0, ref state.work1);
+                state.betahs = v/vv;
+                
+                //
+                // Choose BetaK
+                //
+                if( state.cgtype==0 )
+                {
+                    betak = state.betady;
+                }
+                if( state.cgtype==1 )
+                {
+                    betak = Math.Max(0, Math.Min(state.betady, state.betahs));
+                }
+            }
+            else
+            {
+                
+                //
+                // Something is wrong (may be function is too wild or too flat)
+                // or we just have to restart algo.
+                //
+                // We'll set BetaK=0, which will restart CG algorithm.
+                // We can stop later (during normal checks) if stopping conditions are met.
+                //
+                betak = 0;
+                state.debugrestartscount = state.debugrestartscount+1;
+            }
+            if( state.repiterationscount>0 && state.repiterationscount%(3+n)==0 )
+            {
+                
+                //
+                // clear Beta every N iterations
+                //
+                betak = 0;
+            }
+            if( state.mcinfo==1 || state.mcinfo==5 )
+            {
+                state.rstimer = rscountdownlen;
+            }
+            else
+            {
+                state.rstimer = state.rstimer-1;
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.dn[i_] = -state.g[i_];
+            }
+            preconditionedmultiply(state, ref state.dn, ref state.work0, ref state.work1);
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.dn[i_] = state.dn[i_] + betak*state.dk[i_];
+            }
+            state.lastscaledstep = 0.0;
+            for(i=0; i<=n-1; i++)
+            {
+                state.lastscaledstep = state.lastscaledstep+math.sqr(state.d[i]/state.s[i]);
+            }
+            state.lastscaledstep = state.stp*Math.Sqrt(state.lastscaledstep);
+            if( state.mcinfo==1 )
+            {
+                
+                //
+                // Step is good (Wolfe conditions hold), update LastGoodStep.
+                //
+                // This check for MCINFO=1 is essential because sometimes in the
+                // constrained optimization setting we may take very short steps
+                // (like 1E-15) because we were very close to boundary of the
+                // feasible area. Such short step does not mean that we've converged
+                // to the solution - it was so short because we were close to the
+                // boundary and there was a limit on step length.
+                //
+                // So having such short step is quite normal situation. However, we
+                // should NOT start next iteration from step whose initial length is
+                // estimated as 1E-15 because it may lead to the failure of the
+                // linear minimizer (step is too short, function does not changes,
+                // line search stagnates).
+                //
+                state.lastgoodstep = 0;
+                for(i=0; i<=n-1; i++)
+                {
+                    state.lastgoodstep = state.lastgoodstep+math.sqr(state.d[i]);
+                }
+                state.lastgoodstep = state.stp*Math.Sqrt(state.lastgoodstep);
+            }
+            
+            //
+            // Update information.
+            // Check stopping conditions.
+            //
+            state.repnfev = state.repnfev+state.nfev;
+            state.repiterationscount = state.repiterationscount+1;
+            if( state.repiterationscount>=state.maxits && state.maxits>0 )
+            {
+                
+                //
+                // Too many iterations
+                //
+                state.repterminationtype = 5;
+                result = false;
+                return result;
+            }
+            v = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                v = v+math.sqr(state.g[i]*state.s[i]);
+            }
+            if( (double)(Math.Sqrt(v))<=(double)(state.epsg) )
+            {
+                
+                //
+                // Gradient is small enough
+                //
+                state.repterminationtype = 4;
+                result = false;
+                return result;
+            }
+            if( !state.innerresetneeded )
+            {
+                
+                //
+                // These conditions are checked only when no inner reset was requested by user
+                //
+                if( (double)(state.fold-state.f)<=(double)(state.epsf*Math.Max(Math.Abs(state.fold), Math.Max(Math.Abs(state.f), 1.0))) )
+                {
+                    
+                    //
+                    // F(k+1)-F(k) is small enough
+                    //
+                    state.repterminationtype = 1;
+                    result = false;
+                    return result;
+                }
+                if( (double)(state.lastscaledstep)<=(double)(state.epsx) )
+                {
+                    
+                    //
+                    // X(k+1)-X(k) is small enough
+                    //
+                    state.repterminationtype = 2;
+                    result = false;
+                    return result;
+                }
+            }
+            if( state.rstimer<=0 )
+            {
+                
+                //
+                // Too many subsequent restarts
+                //
+                state.repterminationtype = 7;
+                result = false;
+                return result;
+            }
+            
+            //
+            // Shift Xk/Dk, update other information
+            //
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.xk[i_] = state.xn[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.dk[i_] = state.dn[i_];
+            }
+            state.fold = state.f;
+            state.k = state.k+1;
+            goto lbl_26;
+        lbl_27:
+            result = false;
+            return result;
+            
+            //
+            // Saving state
+            //
+        lbl_rcomm:
+            result = true;
+            state.rstate.ia[0] = n;
+            state.rstate.ia[1] = i;
+            state.rstate.ra[0] = betak;
+            state.rstate.ra[1] = v;
+            state.rstate.ra[2] = vv;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Conjugate gradient results
+
+        INPUT PARAMETERS:
+            State   -   algorithm state
+
+        OUTPUT PARAMETERS:
+            X       -   array[0..N-1], solution
+            Rep     -   optimization report:
+                        * Rep.TerminationType completetion code:
+                            *  1    relative function improvement is no more than
+                                    EpsF.
+                            *  2    relative step is no more than EpsX.
+                            *  4    gradient norm is no more than EpsG
+                            *  5    MaxIts steps was taken
+                            *  7    stopping conditions are too stringent,
+                                    further improvement is impossible,
+                                    we return best X found so far
+                            *  8    terminated by user
+                        * Rep.IterationsCount contains iterations count
+                        * NFEV countains number of function calculations
+
+          -- ALGLIB --
+             Copyright 20.04.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mincgresults(mincgstate state,
+            ref double[] x,
+            mincgreport rep)
+        {
+            x = new double[0];
+
+            mincgresultsbuf(state, ref x, rep);
+        }
+
+
+        /*************************************************************************
+        Conjugate gradient results
+
+        Buffered implementation of MinCGResults(), which uses pre-allocated buffer
+        to store X[]. If buffer size is  too  small,  it  resizes  buffer.  It  is
+        intended to be used in the inner cycles of performance critical algorithms
+        where array reallocation penalty is too large to be ignored.
+
+          -- ALGLIB --
+             Copyright 20.04.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mincgresultsbuf(mincgstate state,
+            ref double[] x,
+            mincgreport rep)
+        {
+            int i_ = 0;
+
+            if( alglib.ap.len(x)<state.n )
+            {
+                x = new double[state.n];
+            }
+            for(i_=0; i_<=state.n-1;i_++)
+            {
+                x[i_] = state.xn[i_];
+            }
+            rep.iterationscount = state.repiterationscount;
+            rep.nfev = state.repnfev;
+            rep.terminationtype = state.repterminationtype;
+        }
+
+
+        /*************************************************************************
+        This  subroutine  restarts  CG  algorithm from new point. All optimization
+        parameters are left unchanged.
+
+        This  function  allows  to  solve multiple  optimization  problems  (which
+        must have same number of dimensions) without object reallocation penalty.
+
+        INPUT PARAMETERS:
+            State   -   structure used to store algorithm state.
+            X       -   new starting point.
+
+          -- ALGLIB --
+             Copyright 30.07.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mincgrestartfrom(mincgstate state,
+            double[] x)
+        {
+            int i_ = 0;
+
+            alglib.ap.assert(alglib.ap.len(x)>=state.n, "MinCGRestartFrom: Length(X)<N!");
+            alglib.ap.assert(apserv.isfinitevector(x, state.n), "MinCGCreate: X contains infinite or NaN values!");
+            for(i_=0; i_<=state.n-1;i_++)
+            {
+                state.x[i_] = x[i_];
+            }
+            mincgsuggeststep(state, 0.0);
+            state.rstate.ia = new int[1+1];
+            state.rstate.ra = new double[2+1];
+            state.rstate.stage = -1;
+            clearrequestfields(state);
+        }
+
+
+        /*************************************************************************
+        Faster version of MinCGSetPrecDiag(), for time-critical parts of code,
+        without safety checks.
+
+          -- ALGLIB --
+             Copyright 13.10.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mincgsetprecdiagfast(mincgstate state,
+            double[] d)
+        {
+            int i = 0;
+
+            apserv.rvectorsetlengthatleast(ref state.diagh, state.n);
+            apserv.rvectorsetlengthatleast(ref state.diaghl2, state.n);
+            state.prectype = 2;
+            state.vcnt = 0;
+            state.innerresetneeded = true;
+            for(i=0; i<=state.n-1; i++)
+            {
+                state.diagh[i] = d[i];
+                state.diaghl2[i] = 0.0;
+            }
+        }
+
+
+        /*************************************************************************
+        This function sets low-rank preconditioner for Hessian matrix  H=D+V'*C*V,
+        where:
+        * H is a Hessian matrix, which is approximated by D/V/C
+        * D=D1+D2 is a diagonal matrix, which includes two positive definite terms:
+          * constant term D1 (is not updated or infrequently updated)
+          * variable term D2 (can be cheaply updated from iteration to iteration)
+        * V is a low-rank correction
+        * C is a diagonal factor of low-rank correction
+
+        Preconditioner P is calculated using approximate Woodburry formula:
+            P  = D^(-1) - D^(-1)*V'*(C^(-1)+V*D1^(-1)*V')^(-1)*V*D^(-1)
+               = D^(-1) - D^(-1)*VC'*VC*D^(-1),
+        where
+            VC = sqrt(B)*V
+            B  = (C^(-1)+V*D1^(-1)*V')^(-1)
+            
+        Note that B is calculated using constant term (D1) only,  which  allows us
+        to update D2 without recalculation of B or   VC.  Such  preconditioner  is
+        exact when D2 is zero. When D2 is non-zero, it is only approximation,  but
+        very good and cheap one.
+
+        This function accepts D1, V, C.
+        D2 is set to zero by default.
+
+        Cost of this update is O(N*VCnt*VCnt), but D2 can be updated in just O(N)
+        by MinCGSetPrecVarPart.
+
+          -- ALGLIB --
+             Copyright 13.10.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mincgsetpreclowrankfast(mincgstate state,
+            double[] d1,
+            double[] c,
+            double[,] v,
+            int vcnt)
+        {
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int n = 0;
+            double t = 0;
+            double[,] b = new double[0,0];
+            int i_ = 0;
+
+            if( vcnt==0 )
+            {
+                mincgsetprecdiagfast(state, d1);
+                return;
+            }
+            n = state.n;
+            b = new double[vcnt, vcnt];
+            apserv.rvectorsetlengthatleast(ref state.diagh, n);
+            apserv.rvectorsetlengthatleast(ref state.diaghl2, n);
+            apserv.rmatrixsetlengthatleast(ref state.vcorr, vcnt, n);
+            state.prectype = 2;
+            state.vcnt = vcnt;
+            state.innerresetneeded = true;
+            for(i=0; i<=n-1; i++)
+            {
+                state.diagh[i] = d1[i];
+                state.diaghl2[i] = 0.0;
+            }
+            for(i=0; i<=vcnt-1; i++)
+            {
+                for(j=i; j<=vcnt-1; j++)
+                {
+                    t = 0;
+                    for(k=0; k<=n-1; k++)
+                    {
+                        t = t+v[i,k]*v[j,k]/d1[k];
+                    }
+                    b[i,j] = t;
+                }
+                b[i,i] = b[i,i]+1.0/c[i];
+            }
+            if( !trfac.spdmatrixcholeskyrec(ref b, 0, vcnt, true, ref state.work0) )
+            {
+                state.vcnt = 0;
+                return;
+            }
+            for(i=0; i<=vcnt-1; i++)
+            {
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    state.vcorr[i,i_] = v[i,i_];
+                }
+                for(j=0; j<=i-1; j++)
+                {
+                    t = b[j,i];
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        state.vcorr[i,i_] = state.vcorr[i,i_] - t*state.vcorr[j,i_];
+                    }
+                }
+                t = 1/b[i,i];
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    state.vcorr[i,i_] = t*state.vcorr[i,i_];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This function updates variable part (diagonal matrix D2)
+        of low-rank preconditioner.
+
+        This update is very cheap and takes just O(N) time.
+
+        It has no effect with default preconditioner.
+
+          -- ALGLIB --
+             Copyright 13.10.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mincgsetprecvarpart(mincgstate state,
+            double[] d2)
+        {
+            int i = 0;
+            int n = 0;
+
+            n = state.n;
+            for(i=0; i<=n-1; i++)
+            {
+                state.diaghl2[i] = d2[i];
+            }
+        }
+
+
+        /*************************************************************************
+        Clears request fileds (to be sure that we don't forgot to clear something)
+        *************************************************************************/
+        private static void clearrequestfields(mincgstate state)
+        {
+            state.needf = false;
+            state.needfg = false;
+            state.xupdated = false;
+            state.lsstart = false;
+            state.lsend = false;
+            state.algpowerup = false;
+        }
+
+
+        /*************************************************************************
+        This function calculates preconditioned product H^(-1)*x and stores result
+        back into X. Work0[] and Work1[] are used as temporaries (size must be at
+        least N; this function doesn't allocate arrays).
+
+          -- ALGLIB --
+             Copyright 13.10.2010 by Bochkanov Sergey
+        *************************************************************************/
+        private static void preconditionedmultiply(mincgstate state,
+            ref double[] x,
+            ref double[] work0,
+            ref double[] work1)
+        {
+            int i = 0;
+            int n = 0;
+            int vcnt = 0;
+            double v = 0;
+            int i_ = 0;
+
+            n = state.n;
+            vcnt = state.vcnt;
+            if( state.prectype==0 )
+            {
+                return;
+            }
+            if( state.prectype==3 )
+            {
+                for(i=0; i<=n-1; i++)
+                {
+                    x[i] = x[i]*state.s[i]*state.s[i];
+                }
+                return;
+            }
+            alglib.ap.assert(state.prectype==2, "MinCG: internal error (unexpected PrecType)");
+            
+            //
+            // handle part common for VCnt=0 and VCnt<>0
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                x[i] = x[i]/(state.diagh[i]+state.diaghl2[i]);
+            }
+            
+            //
+            // if VCnt>0
+            //
+            if( vcnt>0 )
+            {
+                for(i=0; i<=vcnt-1; i++)
+                {
+                    v = 0.0;
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        v += state.vcorr[i,i_]*x[i_];
+                    }
+                    work0[i] = v;
+                }
+                for(i=0; i<=n-1; i++)
+                {
+                    work1[i] = 0;
+                }
+                for(i=0; i<=vcnt-1; i++)
+                {
+                    v = work0[i];
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        state.work1[i_] = state.work1[i_] + v*state.vcorr[i,i_];
+                    }
+                }
+                for(i=0; i<=n-1; i++)
+                {
+                    x[i] = x[i]-state.work1[i]/(state.diagh[i]+state.diaghl2[i]);
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This function calculates preconditioned product x'*H^(-1)*y. Work0[] and
+        Work1[] are used as temporaries (size must be at least N; this function
+        doesn't allocate arrays).
+
+          -- ALGLIB --
+             Copyright 13.10.2010 by Bochkanov Sergey
+        *************************************************************************/
+        private static double preconditionedmultiply2(mincgstate state,
+            ref double[] x,
+            ref double[] y,
+            ref double[] work0,
+            ref double[] work1)
+        {
+            double result = 0;
+            int i = 0;
+            int n = 0;
+            int vcnt = 0;
+            double v0 = 0;
+            double v1 = 0;
+            int i_ = 0;
+
+            n = state.n;
+            vcnt = state.vcnt;
+            
+            //
+            // no preconditioning
+            //
+            if( state.prectype==0 )
+            {
+                v0 = 0.0;
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    v0 += x[i_]*y[i_];
+                }
+                result = v0;
+                return result;
+            }
+            if( state.prectype==3 )
+            {
+                result = 0;
+                for(i=0; i<=n-1; i++)
+                {
+                    result = result+x[i]*state.s[i]*state.s[i]*y[i];
+                }
+                return result;
+            }
+            alglib.ap.assert(state.prectype==2, "MinCG: internal error (unexpected PrecType)");
+            
+            //
+            // low rank preconditioning
+            //
+            result = 0.0;
+            for(i=0; i<=n-1; i++)
+            {
+                result = result+x[i]*y[i]/(state.diagh[i]+state.diaghl2[i]);
+            }
+            if( vcnt>0 )
+            {
+                for(i=0; i<=n-1; i++)
+                {
+                    work0[i] = x[i]/(state.diagh[i]+state.diaghl2[i]);
+                    work1[i] = y[i]/(state.diagh[i]+state.diaghl2[i]);
+                }
+                for(i=0; i<=vcnt-1; i++)
+                {
+                    v0 = 0.0;
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        v0 += work0[i_]*state.vcorr[i,i_];
+                    }
+                    v1 = 0.0;
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        v1 += work1[i_]*state.vcorr[i,i_];
+                    }
+                    result = result-v0*v1;
+                }
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        Internal initialization subroutine
+
+          -- ALGLIB --
+             Copyright 16.05.2011 by Bochkanov Sergey
+        *************************************************************************/
+        private static void mincginitinternal(int n,
+            double diffstep,
+            mincgstate state)
+        {
+            int i = 0;
+
+            state.n = n;
+            state.diffstep = diffstep;
+            mincgsetcond(state, 0, 0, 0, 0);
+            mincgsetxrep(state, false);
+            mincgsetdrep(state, false);
+            mincgsetstpmax(state, 0);
+            mincgsetcgtype(state, -1);
+            mincgsetprecdefault(state);
+            state.xk = new double[n];
+            state.dk = new double[n];
+            state.xn = new double[n];
+            state.dn = new double[n];
+            state.x = new double[n];
+            state.d = new double[n];
+            state.g = new double[n];
+            state.work0 = new double[n];
+            state.work1 = new double[n];
+            state.yk = new double[n];
+            state.s = new double[n];
+            for(i=0; i<=n-1; i++)
+            {
+                state.s[i] = 1.0;
+            }
+        }
+
+
+    }
+    public class minbleic
+    {
+        /*************************************************************************
+        This object stores nonlinear optimizer state.
+        You should use functions provided by MinBLEIC subpackage to work with this
+        object
+        *************************************************************************/
+        public class minbleicstate
+        {
+            public int nmain;
+            public int nslack;
+            public double innerepsg;
+            public double innerepsf;
+            public double innerepsx;
+            public double outerepsx;
+            public double outerepsi;
+            public int maxits;
+            public bool xrep;
+            public double stpmax;
+            public double diffstep;
+            public int prectype;
+            public double[] diaghoriginal;
+            public double[] diagh;
+            public double[] x;
+            public double f;
+            public double[] g;
+            public bool needf;
+            public bool needfg;
+            public bool xupdated;
+            public rcommstate rstate;
+            public int repinneriterationscount;
+            public int repouteriterationscount;
+            public int repnfev;
+            public int repterminationtype;
+            public double repdebugeqerr;
+            public double repdebugfs;
+            public double repdebugff;
+            public double repdebugdx;
+            public int repdebugfeasqpits;
+            public int repdebugfeasgpaits;
+            public double[] xcur;
+            public double[] xprev;
+            public double[] xstart;
+            public int itsleft;
+            public double[] xend;
+            public double[] lastg;
+            public double trimthreshold;
+            public double[,] ceoriginal;
+            public double[,] ceeffective;
+            public double[,] cecurrent;
+            public int[] ct;
+            public int cecnt;
+            public int cedim;
+            public double[] xe;
+            public bool[] hasbndl;
+            public bool[] hasbndu;
+            public double[] bndloriginal;
+            public double[] bnduoriginal;
+            public double[] bndleffective;
+            public double[] bndueffective;
+            public bool[] activeconstraints;
+            public double[] constrainedvalues;
+            public double[] transforms;
+            public double[] seffective;
+            public double[] soriginal;
+            public double[] w;
+            public double[] tmp0;
+            public double[] tmp1;
+            public double[] tmp2;
+            public double[] r;
+            public double[,] lmmatrix;
+            public double v0;
+            public double v1;
+            public double v2;
+            public double t;
+            public double errfeas;
+            public double gnorm;
+            public double mpgnorm;
+            public double mba;
+            public int variabletofreeze;
+            public double valuetofreeze;
+            public double fbase;
+            public double fm2;
+            public double fm1;
+            public double fp1;
+            public double fp2;
+            public double xm1;
+            public double xp1;
+            public mincg.mincgstate cgstate;
+            public mincg.mincgreport cgrep;
+            public int optdim;
+            public minbleicstate()
+            {
+                diaghoriginal = new double[0];
+                diagh = new double[0];
+                x = new double[0];
+                g = new double[0];
+                rstate = new rcommstate();
+                xcur = new double[0];
+                xprev = new double[0];
+                xstart = new double[0];
+                xend = new double[0];
+                lastg = new double[0];
+                ceoriginal = new double[0,0];
+                ceeffective = new double[0,0];
+                cecurrent = new double[0,0];
+                ct = new int[0];
+                xe = new double[0];
+                hasbndl = new bool[0];
+                hasbndu = new bool[0];
+                bndloriginal = new double[0];
+                bnduoriginal = new double[0];
+                bndleffective = new double[0];
+                bndueffective = new double[0];
+                activeconstraints = new bool[0];
+                constrainedvalues = new double[0];
+                transforms = new double[0];
+                seffective = new double[0];
+                soriginal = new double[0];
+                w = new double[0];
+                tmp0 = new double[0];
+                tmp1 = new double[0];
+                tmp2 = new double[0];
+                r = new double[0];
+                lmmatrix = new double[0,0];
+                cgstate = new mincg.mincgstate();
+                cgrep = new mincg.mincgreport();
+            }
+        };
+
+
+        /*************************************************************************
+        This structure stores optimization report:
+        * InnerIterationsCount      number of inner iterations
+        * OuterIterationsCount      number of outer iterations
+        * NFEV                      number of gradient evaluations
+        * TerminationType           termination type (see below)
+
+        TERMINATION CODES
+
+        TerminationType field contains completion code, which can be:
+          -10   unsupported combination of algorithm settings:
+                1) StpMax is set to non-zero value,
+                AND 2) non-default preconditioner is used.
+                You can't use both features at the same moment,
+                so you have to choose one of them (and to turn
+                off another one).
+          -3    inconsistent constraints. Feasible point is
+                either nonexistent or too hard to find. Try to
+                restart optimizer with better initial
+                approximation
+           4    conditions on constraints are fulfilled
+                with error less than or equal to EpsC
+           5    MaxIts steps was taken
+           7    stopping conditions are too stringent,
+                further improvement is impossible,
+                X contains best point found so far.
+
+        ADDITIONAL FIELDS
+
+        There are additional fields which can be used for debugging:
+        * DebugEqErr                error in the equality constraints (2-norm)
+        * DebugFS                   f, calculated at projection of initial point
+                                    to the feasible set
+        * DebugFF                   f, calculated at the final point
+        * DebugDX                   |X_start-X_final|
+        *************************************************************************/
+        public class minbleicreport
+        {
+            public int inneriterationscount;
+            public int outeriterationscount;
+            public int nfev;
+            public int terminationtype;
+            public double debugeqerr;
+            public double debugfs;
+            public double debugff;
+            public double debugdx;
+            public int debugfeasqpits;
+            public int debugfeasgpaits;
+        };
+
+
+
+
+        public const double svdtol = 100;
+        public const double maxouterits = 20;
+        public const double maxnonmonotoniclen = 1.0E5;
+
+
+        /*************************************************************************
+                             BOUND CONSTRAINED OPTIMIZATION
+               WITH ADDITIONAL LINEAR EQUALITY AND INEQUALITY CONSTRAINTS
+
+        DESCRIPTION:
+        The  subroutine  minimizes  function   F(x)  of N arguments subject to any
+        combination of:
+        * bound constraints
+        * linear inequality constraints
+        * linear equality constraints
+
+        REQUIREMENTS:
+        * user must provide function value and gradient
+        * starting point X0 must be feasible or
+          not too far away from the feasible set
+        * grad(f) must be Lipschitz continuous on a level set:
+          L = { x : f(x)<=f(x0) }
+        * function must be defined everywhere on the feasible set F
+
+        USAGE:
+
+        Constrained optimization if far more complex than the unconstrained one.
+        Here we give very brief outline of the BLEIC optimizer. We strongly recommend
+        you to read examples in the ALGLIB Reference Manual and to read ALGLIB User Guide
+        on optimization, which is available at http://www.alglib.net/optimization/
+
+        1. User initializes algorithm state with MinBLEICCreate() call
+
+        2. USer adds boundary and/or linear constraints by calling
+           MinBLEICSetBC() and MinBLEICSetLC() functions.
+
+        3. User sets stopping conditions for underlying unconstrained solver
+           with MinBLEICSetInnerCond() call.
+           This function controls accuracy of underlying optimization algorithm.
+
+        4. User sets stopping conditions for outer iteration by calling
+           MinBLEICSetOuterCond() function.
+           This function controls handling of boundary and inequality constraints.
+
+        5. Additionally, user may set limit on number of internal iterations
+           by MinBLEICSetMaxIts() call.
+           This function allows to prevent algorithm from looping forever.
+
+        6. User calls MinBLEICOptimize() function which takes algorithm  state and
+           pointer (delegate, etc.) to callback function which calculates F/G.
+
+        7. User calls MinBLEICResults() to get solution
+
+        8. Optionally user may call MinBLEICRestartFrom() to solve another problem
+           with same N but another starting point.
+           MinBLEICRestartFrom() allows to reuse already initialized structure.
+
+
+        INPUT PARAMETERS:
+            N       -   problem dimension, N>0:
+                        * if given, only leading N elements of X are used
+                        * if not given, automatically determined from size ofX
+            X       -   starting point, array[N]:
+                        * it is better to set X to a feasible point
+                        * but X can be infeasible, in which case algorithm will try
+                          to find feasible point first, using X as initial
+                          approximation.
+
+        OUTPUT PARAMETERS:
+            State   -   structure stores algorithm state
+
+          -- ALGLIB --
+             Copyright 28.11.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minbleiccreate(int n,
+            double[] x,
+            minbleicstate state)
+        {
+            double[,] c = new double[0,0];
+            int[] ct = new int[0];
+
+            alglib.ap.assert(n>=1, "MinBLEICCreate: N<1");
+            alglib.ap.assert(alglib.ap.len(x)>=n, "MinBLEICCreate: Length(X)<N");
+            alglib.ap.assert(apserv.isfinitevector(x, n), "MinBLEICCreate: X contains infinite or NaN values!");
+            minbleicinitinternal(n, x, 0.0, state);
+        }
+
+
+        /*************************************************************************
+        The subroutine is finite difference variant of MinBLEICCreate().  It  uses
+        finite differences in order to differentiate target function.
+
+        Description below contains information which is specific to  this function
+        only. We recommend to read comments on MinBLEICCreate() in  order  to  get
+        more information about creation of BLEIC optimizer.
+
+        INPUT PARAMETERS:
+            N       -   problem dimension, N>0:
+                        * if given, only leading N elements of X are used
+                        * if not given, automatically determined from size of X
+            X       -   starting point, array[0..N-1].
+            DiffStep-   differentiation step, >0
+
+        OUTPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+
+        NOTES:
+        1. algorithm uses 4-point central formula for differentiation.
+        2. differentiation step along I-th axis is equal to DiffStep*S[I] where
+           S[] is scaling vector which can be set by MinBLEICSetScale() call.
+        3. we recommend you to use moderate values of  differentiation  step.  Too
+           large step will result in too large truncation  errors, while too small
+           step will result in too large numerical  errors.  1.0E-6  can  be  good
+           value to start with.
+        4. Numerical  differentiation  is   very   inefficient  -   one   gradient
+           calculation needs 4*N function evaluations. This function will work for
+           any N - either small (1...10), moderate (10...100) or  large  (100...).
+           However, performance penalty will be too severe for any N's except  for
+           small ones.
+           We should also say that code which relies on numerical  differentiation
+           is  less  robust and precise. CG needs exact gradient values. Imprecise
+           gradient may slow  down  convergence, especially  on  highly  nonlinear
+           problems.
+           Thus  we  recommend to use this function for fast prototyping on small-
+           dimensional problems only, and to implement analytical gradient as soon
+           as possible.
+
+          -- ALGLIB --
+             Copyright 16.05.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minbleiccreatef(int n,
+            double[] x,
+            double diffstep,
+            minbleicstate state)
+        {
+            double[,] c = new double[0,0];
+            int[] ct = new int[0];
+
+            alglib.ap.assert(n>=1, "MinBLEICCreateF: N<1");
+            alglib.ap.assert(alglib.ap.len(x)>=n, "MinBLEICCreateF: Length(X)<N");
+            alglib.ap.assert(apserv.isfinitevector(x, n), "MinBLEICCreateF: X contains infinite or NaN values!");
+            alglib.ap.assert(math.isfinite(diffstep), "MinBLEICCreateF: DiffStep is infinite or NaN!");
+            alglib.ap.assert((double)(diffstep)>(double)(0), "MinBLEICCreateF: DiffStep is non-positive!");
+            minbleicinitinternal(n, x, diffstep, state);
+        }
+
+
+        /*************************************************************************
+        This function sets boundary constraints for BLEIC optimizer.
+
+        Boundary constraints are inactive by default (after initial creation).
+        They are preserved after algorithm restart with MinBLEICRestartFrom().
+
+        INPUT PARAMETERS:
+            State   -   structure stores algorithm state
+            BndL    -   lower bounds, array[N].
+                        If some (all) variables are unbounded, you may specify
+                        very small number or -INF.
+            BndU    -   upper bounds, array[N].
+                        If some (all) variables are unbounded, you may specify
+                        very large number or +INF.
+
+        NOTE 1: it is possible to specify BndL[i]=BndU[i]. In this case I-th
+        variable will be "frozen" at X[i]=BndL[i]=BndU[i].
+
+        NOTE 2: this solver has following useful properties:
+        * bound constraints are always satisfied exactly
+        * function is evaluated only INSIDE area specified by  bound  constraints,
+          even  when  numerical  differentiation is used (algorithm adjusts  nodes
+          according to boundary constraints)
+
+          -- ALGLIB --
+             Copyright 28.11.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minbleicsetbc(minbleicstate state,
+            double[] bndl,
+            double[] bndu)
+        {
+            int i = 0;
+            int n = 0;
+
+            n = state.nmain;
+            alglib.ap.assert(alglib.ap.len(bndl)>=n, "MinBLEICSetBC: Length(BndL)<N");
+            alglib.ap.assert(alglib.ap.len(bndu)>=n, "MinBLEICSetBC: Length(BndU)<N");
+            for(i=0; i<=n-1; i++)
+            {
+                alglib.ap.assert(math.isfinite(bndl[i]) || Double.IsNegativeInfinity(bndl[i]), "MinBLEICSetBC: BndL contains NAN or +INF");
+                alglib.ap.assert(math.isfinite(bndu[i]) || Double.IsPositiveInfinity(bndu[i]), "MinBLEICSetBC: BndL contains NAN or -INF");
+                state.bndloriginal[i] = bndl[i];
+                state.hasbndl[i] = math.isfinite(bndl[i]);
+                state.bnduoriginal[i] = bndu[i];
+                state.hasbndu[i] = math.isfinite(bndu[i]);
+            }
+        }
+
+
+        /*************************************************************************
+        This function sets linear constraints for BLEIC optimizer.
+
+        Linear constraints are inactive by default (after initial creation).
+        They are preserved after algorithm restart with MinBLEICRestartFrom().
+
+        INPUT PARAMETERS:
+            State   -   structure previously allocated with MinBLEICCreate call.
+            C       -   linear constraints, array[K,N+1].
+                        Each row of C represents one constraint, either equality
+                        or inequality (see below):
+                        * first N elements correspond to coefficients,
+                        * last element corresponds to the right part.
+                        All elements of C (including right part) must be finite.
+            CT      -   type of constraints, array[K]:
+                        * if CT[i]>0, then I-th constraint is C[i,*]*x >= C[i,n+1]
+                        * if CT[i]=0, then I-th constraint is C[i,*]*x  = C[i,n+1]
+                        * if CT[i]<0, then I-th constraint is C[i,*]*x <= C[i,n+1]
+            K       -   number of equality/inequality constraints, K>=0:
+                        * if given, only leading K elements of C/CT are used
+                        * if not given, automatically determined from sizes of C/CT
+
+        NOTE 1: linear (non-bound) constraints are satisfied only approximately:
+        * there always exists some minor violation (about Epsilon in magnitude)
+          due to rounding errors
+        * numerical differentiation, if used, may  lead  to  function  evaluations
+          outside  of the feasible  area,   because   algorithm  does  NOT  change
+          numerical differentiation formula according to linear constraints.
+        If you want constraints to be  satisfied  exactly, try to reformulate your
+        problem  in  such  manner  that  all constraints will become boundary ones
+        (this kind of constraints is always satisfied exactly, both in  the  final
+        solution and in all intermediate points).
+
+          -- ALGLIB --
+             Copyright 28.11.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minbleicsetlc(minbleicstate state,
+            double[,] c,
+            int[] ct,
+            int k)
+        {
+            int nmain = 0;
+            int i = 0;
+            int i_ = 0;
+
+            nmain = state.nmain;
+            
+            //
+            // First, check for errors in the inputs
+            //
+            alglib.ap.assert(k>=0, "MinBLEICSetLC: K<0");
+            alglib.ap.assert(alglib.ap.cols(c)>=nmain+1 || k==0, "MinBLEICSetLC: Cols(C)<N+1");
+            alglib.ap.assert(alglib.ap.rows(c)>=k, "MinBLEICSetLC: Rows(C)<K");
+            alglib.ap.assert(alglib.ap.len(ct)>=k, "MinBLEICSetLC: Length(CT)<K");
+            alglib.ap.assert(apserv.apservisfinitematrix(c, k, nmain+1), "MinBLEICSetLC: C contains infinite or NaN values!");
+            
+            //
+            // Determine number of constraints,
+            // allocate space and copy
+            //
+            state.cecnt = k;
+            apserv.rmatrixsetlengthatleast(ref state.ceoriginal, state.cecnt, nmain+1);
+            apserv.ivectorsetlengthatleast(ref state.ct, state.cecnt);
+            for(i=0; i<=k-1; i++)
+            {
+                state.ct[i] = ct[i];
+                for(i_=0; i_<=nmain;i_++)
+                {
+                    state.ceoriginal[i,i_] = c[i,i_];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This function sets stopping conditions for the underlying nonlinear CG
+        optimizer. It controls overall accuracy of solution. These conditions
+        should be strict enough in order for algorithm to converge.
+
+        INPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+            EpsG    -   >=0
+                        The  subroutine  finishes  its  work   if   the  condition
+                        |v|<EpsG is satisfied, where:
+                        * |.| means Euclidian norm
+                        * v - scaled gradient vector, v[i]=g[i]*s[i]
+                        * g - gradient
+                        * s - scaling coefficients set by MinBLEICSetScale()
+            EpsF    -   >=0
+                        The  subroutine  finishes  its work if on k+1-th iteration
+                        the  condition  |F(k+1)-F(k)|<=EpsF*max{|F(k)|,|F(k+1)|,1}
+                        is satisfied.
+            EpsX    -   >=0
+                        The subroutine finishes its work if  on  k+1-th  iteration
+                        the condition |v|<=EpsX is fulfilled, where:
+                        * |.| means Euclidian norm
+                        * v - scaled step vector, v[i]=dx[i]/s[i]
+                        * dx - ste pvector, dx=X(k+1)-X(k)
+                        * s - scaling coefficients set by MinBLEICSetScale()
+
+        Passing EpsG=0, EpsF=0 and EpsX=0 (simultaneously) will lead to
+        automatic stopping criterion selection.
+
+        These conditions are used to terminate inner iterations. However, you
+        need to tune termination conditions for outer iterations too.
+
+          -- ALGLIB --
+             Copyright 28.11.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minbleicsetinnercond(minbleicstate state,
+            double epsg,
+            double epsf,
+            double epsx)
+        {
+            alglib.ap.assert(math.isfinite(epsg), "MinBLEICSetInnerCond: EpsG is not finite number");
+            alglib.ap.assert((double)(epsg)>=(double)(0), "MinBLEICSetInnerCond: negative EpsG");
+            alglib.ap.assert(math.isfinite(epsf), "MinBLEICSetInnerCond: EpsF is not finite number");
+            alglib.ap.assert((double)(epsf)>=(double)(0), "MinBLEICSetInnerCond: negative EpsF");
+            alglib.ap.assert(math.isfinite(epsx), "MinBLEICSetInnerCond: EpsX is not finite number");
+            alglib.ap.assert((double)(epsx)>=(double)(0), "MinBLEICSetInnerCond: negative EpsX");
+            state.innerepsg = epsg;
+            state.innerepsf = epsf;
+            state.innerepsx = epsx;
+        }
+
+
+        /*************************************************************************
+        This function sets stopping conditions for outer iteration of BLEIC algo.
+
+        These conditions control accuracy of constraint handling and amount of
+        infeasibility allowed in the solution.
+
+        INPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+            EpsX    -   >0, stopping condition on outer iteration step length
+            EpsI    -   >0, stopping condition on infeasibility
+            
+        Both EpsX and EpsI must be non-zero.
+
+        MEANING OF EpsX
+
+        EpsX  is  a  stopping  condition for outer iterations. Algorithm will stop
+        when  solution  of  the  current  modified  subproblem will be within EpsX
+        (using 2-norm) of the previous solution.
+
+        MEANING OF EpsI
+
+        EpsI controls feasibility properties -  algorithm  won't  stop  until  all
+        inequality constraints will be satisfied with error (distance from current
+        point to the feasible area) at most EpsI.
+
+          -- ALGLIB --
+             Copyright 28.11.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minbleicsetoutercond(minbleicstate state,
+            double epsx,
+            double epsi)
+        {
+            alglib.ap.assert(math.isfinite(epsx), "MinBLEICSetOuterCond: EpsX is not finite number");
+            alglib.ap.assert((double)(epsx)>(double)(0), "MinBLEICSetOuterCond: non-positive EpsX");
+            alglib.ap.assert(math.isfinite(epsi), "MinBLEICSetOuterCond: EpsI is not finite number");
+            alglib.ap.assert((double)(epsi)>(double)(0), "MinBLEICSetOuterCond: non-positive EpsI");
+            state.outerepsx = epsx;
+            state.outerepsi = epsi;
+        }
+
+
+        /*************************************************************************
+        This function sets scaling coefficients for BLEIC optimizer.
+
+        ALGLIB optimizers use scaling matrices to test stopping  conditions  (step
+        size and gradient are scaled before comparison with tolerances).  Scale of
+        the I-th variable is a translation invariant measure of:
+        a) "how large" the variable is
+        b) how large the step should be to make significant changes in the function
+
+        Scaling is also used by finite difference variant of the optimizer  - step
+        along I-th axis is equal to DiffStep*S[I].
+
+        In  most  optimizers  (and  in  the  BLEIC  too)  scaling is NOT a form of
+        preconditioning. It just  affects  stopping  conditions.  You  should  set
+        preconditioner  by  separate  call  to  one  of  the  MinBLEICSetPrec...()
+        functions.
+
+        There is a special  preconditioning  mode, however,  which  uses   scaling
+        coefficients to form diagonal preconditioning matrix. You  can  turn  this
+        mode on, if you want.   But  you should understand that scaling is not the
+        same thing as preconditioning - these are two different, although  related
+        forms of tuning solver.
+
+        INPUT PARAMETERS:
+            State   -   structure stores algorithm state
+            S       -   array[N], non-zero scaling coefficients
+                        S[i] may be negative, sign doesn't matter.
+
+          -- ALGLIB --
+             Copyright 14.01.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minbleicsetscale(minbleicstate state,
+            double[] s)
+        {
+            int i = 0;
+
+            alglib.ap.assert(alglib.ap.len(s)>=state.nmain, "MinBLEICSetScale: Length(S)<N");
+            for(i=0; i<=state.nmain-1; i++)
+            {
+                alglib.ap.assert(math.isfinite(s[i]), "MinBLEICSetScale: S contains infinite or NAN elements");
+                alglib.ap.assert((double)(s[i])!=(double)(0), "MinBLEICSetScale: S contains zero elements");
+                state.soriginal[i] = Math.Abs(s[i]);
+            }
+        }
+
+
+        /*************************************************************************
+        Modification of the preconditioner: preconditioning is turned off.
+
+        INPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+
+          -- ALGLIB --
+             Copyright 13.10.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minbleicsetprecdefault(minbleicstate state)
+        {
+            state.prectype = 0;
+        }
+
+
+        /*************************************************************************
+        Modification  of  the  preconditioner:  diagonal of approximate Hessian is
+        used.
+
+        INPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+            D       -   diagonal of the approximate Hessian, array[0..N-1],
+                        (if larger, only leading N elements are used).
+
+        NOTE 1: D[i] should be positive. Exception will be thrown otherwise.
+
+        NOTE 2: you should pass diagonal of approximate Hessian - NOT ITS INVERSE.
+
+          -- ALGLIB --
+             Copyright 13.10.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minbleicsetprecdiag(minbleicstate state,
+            double[] d)
+        {
+            int i = 0;
+
+            alglib.ap.assert(alglib.ap.len(d)>=state.nmain, "MinBLEICSetPrecDiag: D is too short");
+            for(i=0; i<=state.nmain-1; i++)
+            {
+                alglib.ap.assert(math.isfinite(d[i]), "MinBLEICSetPrecDiag: D contains infinite or NAN elements");
+                alglib.ap.assert((double)(d[i])>(double)(0), "MinBLEICSetPrecDiag: D contains non-positive elements");
+            }
+            apserv.rvectorsetlengthatleast(ref state.diaghoriginal, state.nmain);
+            state.prectype = 2;
+            for(i=0; i<=state.nmain-1; i++)
+            {
+                state.diaghoriginal[i] = d[i];
+            }
+        }
+
+
+        /*************************************************************************
+        Modification of the preconditioner: scale-based diagonal preconditioning.
+
+        This preconditioning mode can be useful when you  don't  have  approximate
+        diagonal of Hessian, but you know that your  variables  are  badly  scaled
+        (for  example,  one  variable is in [1,10], and another in [1000,100000]),
+        and most part of the ill-conditioning comes from different scales of vars.
+
+        In this case simple  scale-based  preconditioner,  with H[i] = 1/(s[i]^2),
+        can greatly improve convergence.
+
+        IMPRTANT: you should set scale of your variables  with  MinBLEICSetScale()
+        call  (before  or after MinBLEICSetPrecScale() call). Without knowledge of
+        the scale of your variables scale-based preconditioner will be  just  unit
+        matrix.
+
+        INPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+
+          -- ALGLIB --
+             Copyright 13.10.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minbleicsetprecscale(minbleicstate state)
+        {
+            state.prectype = 3;
+        }
+
+
+        /*************************************************************************
+        This function allows to stop algorithm after specified number of inner
+        iterations.
+
+        INPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+            MaxIts  -   maximum number of inner iterations.
+                        If MaxIts=0, the number of iterations is unlimited.
+
+          -- ALGLIB --
+             Copyright 28.11.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minbleicsetmaxits(minbleicstate state,
+            int maxits)
+        {
+            alglib.ap.assert(maxits>=0, "MinBLEICSetMaxIts: negative MaxIts!");
+            state.maxits = maxits;
+        }
+
+
+        /*************************************************************************
+        This function turns on/off reporting.
+
+        INPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+            NeedXRep-   whether iteration reports are needed or not
+
+        If NeedXRep is True, algorithm will call rep() callback function if  it is
+        provided to MinBLEICOptimize().
+
+          -- ALGLIB --
+             Copyright 28.11.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minbleicsetxrep(minbleicstate state,
+            bool needxrep)
+        {
+            state.xrep = needxrep;
+        }
+
+
+        /*************************************************************************
+        This function sets maximum step length
+
+        IMPORTANT: this feature is hard to combine with preconditioning. You can't
+        set upper limit on step length, when you solve optimization  problem  with
+        linear (non-boundary) constraints AND preconditioner turned on.
+
+        When  non-boundary  constraints  are  present,  you  have to either a) use
+        preconditioner, or b) use upper limit on step length.  YOU CAN'T USE BOTH!
+        In this case algorithm will terminate with appropriate error code.
+
+        INPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+            StpMax  -   maximum step length, >=0. Set StpMax to 0.0,  if you don't
+                        want to limit step length.
+
+        Use this subroutine when you optimize target function which contains exp()
+        or  other  fast  growing  functions,  and optimization algorithm makes too
+        large  steps  which  lead   to overflow. This function allows us to reject
+        steps  that  are  too  large  (and  therefore  expose  us  to the possible
+        overflow) without actually calculating function value at the x+stp*d.
+
+          -- ALGLIB --
+             Copyright 02.04.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minbleicsetstpmax(minbleicstate state,
+            double stpmax)
+        {
+            alglib.ap.assert(math.isfinite(stpmax), "MinBLEICSetStpMax: StpMax is not finite!");
+            alglib.ap.assert((double)(stpmax)>=(double)(0), "MinBLEICSetStpMax: StpMax<0!");
+            state.stpmax = stpmax;
+        }
+
+
+        /*************************************************************************
+        NOTES:
+
+        1. This function has two different implementations: one which  uses  exact
+           (analytical) user-supplied gradient,  and one which uses function value
+           only  and  numerically  differentiates  function  in  order  to  obtain
+           gradient.
+
+           Depending  on  the  specific  function  used to create optimizer object
+           (either  MinBLEICCreate() for analytical gradient or  MinBLEICCreateF()
+           for numerical differentiation) you should choose appropriate variant of
+           MinBLEICOptimize() - one  which  accepts  function  AND gradient or one
+           which accepts function ONLY.
+
+           Be careful to choose variant of MinBLEICOptimize() which corresponds to
+           your optimization scheme! Table below lists different  combinations  of
+           callback (function/gradient) passed to MinBLEICOptimize()  and specific
+           function used to create optimizer.
+
+
+                             |         USER PASSED TO MinBLEICOptimize()
+           CREATED WITH      |  function only   |  function and gradient
+           ------------------------------------------------------------
+           MinBLEICCreateF() |     work                FAIL
+           MinBLEICCreate()  |     FAIL                work
+
+           Here "FAIL" denotes inappropriate combinations  of  optimizer  creation
+           function  and  MinBLEICOptimize()  version.   Attemps   to   use   such
+           combination (for  example,  to  create optimizer with MinBLEICCreateF()
+           and  to  pass  gradient  information  to  MinCGOptimize()) will lead to
+           exception being thrown. Either  you  did  not pass gradient when it WAS
+           needed or you passed gradient when it was NOT needed.
+
+          -- ALGLIB --
+             Copyright 28.11.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static bool minbleiciteration(minbleicstate state)
+        {
+            bool result = new bool();
+            int nmain = 0;
+            int nslack = 0;
+            int m = 0;
+            int i = 0;
+            int j = 0;
+            double v = 0;
+            double vv = 0;
+            bool b = new bool();
+            int i_ = 0;
+
+            
+            //
+            // Reverse communication preparations
+            // I know it looks ugly, but it works the same way
+            // anywhere from C++ to Python.
+            //
+            // This code initializes locals by:
+            // * random values determined during code
+            //   generation - on first subroutine call
+            // * values from previous call - on subsequent calls
+            //
+            if( state.rstate.stage>=0 )
+            {
+                nmain = state.rstate.ia[0];
+                nslack = state.rstate.ia[1];
+                m = state.rstate.ia[2];
+                i = state.rstate.ia[3];
+                j = state.rstate.ia[4];
+                b = state.rstate.ba[0];
+                v = state.rstate.ra[0];
+                vv = state.rstate.ra[1];
+            }
+            else
+            {
+                nmain = -983;
+                nslack = -989;
+                m = -834;
+                i = 900;
+                j = -287;
+                b = false;
+                v = 214;
+                vv = -338;
+            }
+            if( state.rstate.stage==0 )
+            {
+                goto lbl_0;
+            }
+            if( state.rstate.stage==1 )
+            {
+                goto lbl_1;
+            }
+            if( state.rstate.stage==2 )
+            {
+                goto lbl_2;
+            }
+            if( state.rstate.stage==3 )
+            {
+                goto lbl_3;
+            }
+            if( state.rstate.stage==4 )
+            {
+                goto lbl_4;
+            }
+            if( state.rstate.stage==5 )
+            {
+                goto lbl_5;
+            }
+            if( state.rstate.stage==6 )
+            {
+                goto lbl_6;
+            }
+            if( state.rstate.stage==7 )
+            {
+                goto lbl_7;
+            }
+            if( state.rstate.stage==8 )
+            {
+                goto lbl_8;
+            }
+            if( state.rstate.stage==9 )
+            {
+                goto lbl_9;
+            }
+            if( state.rstate.stage==10 )
+            {
+                goto lbl_10;
+            }
+            if( state.rstate.stage==11 )
+            {
+                goto lbl_11;
+            }
+            if( state.rstate.stage==12 )
+            {
+                goto lbl_12;
+            }
+            
+            //
+            // Routine body
+            //
+            
+            //
+            // Prepare:
+            // * calculate number of slack variables
+            // * initialize locals
+            // * initialize debug fields
+            // * make quick check
+            //
+            nmain = state.nmain;
+            nslack = 0;
+            for(i=0; i<=state.cecnt-1; i++)
+            {
+                if( state.ct[i]!=0 )
+                {
+                    nslack = nslack+1;
+                }
+            }
+            state.nslack = nslack;
+            state.repterminationtype = 0;
+            state.repinneriterationscount = 0;
+            state.repouteriterationscount = 0;
+            state.repnfev = 0;
+            state.repdebugeqerr = 0.0;
+            state.repdebugfs = Double.NaN;
+            state.repdebugff = Double.NaN;
+            state.repdebugdx = Double.NaN;
+            if( (double)(state.stpmax)!=(double)(0) && state.prectype!=0 )
+            {
+                state.repterminationtype = -10;
+                result = false;
+                return result;
+            }
+            
+            //
+            // allocate
+            //
+            apserv.rvectorsetlengthatleast(ref state.r, nmain+nslack);
+            apserv.rvectorsetlengthatleast(ref state.diagh, nmain+nslack);
+            apserv.rvectorsetlengthatleast(ref state.tmp0, nmain+nslack);
+            apserv.rvectorsetlengthatleast(ref state.tmp1, nmain+nslack);
+            apserv.rvectorsetlengthatleast(ref state.tmp2, nmain+nslack);
+            apserv.rmatrixsetlengthatleast(ref state.cecurrent, state.cecnt, nmain+nslack+1);
+            apserv.bvectorsetlengthatleast(ref state.activeconstraints, nmain+nslack);
+            apserv.rvectorsetlengthatleast(ref state.constrainedvalues, nmain+nslack);
+            apserv.rvectorsetlengthatleast(ref state.lastg, nmain+nslack);
+            apserv.rvectorsetlengthatleast(ref state.xe, nmain+nslack);
+            apserv.rvectorsetlengthatleast(ref state.xcur, nmain+nslack);
+            apserv.rvectorsetlengthatleast(ref state.xprev, nmain+nslack);
+            apserv.rvectorsetlengthatleast(ref state.xend, nmain);
+            
+            //
+            // Create/restart optimizer.
+            //
+            // State.OptDim is used to determine current state of optimizer.
+            //
+            if( state.optdim!=nmain+nslack )
+            {
+                for(i=0; i<=nmain+nslack-1; i++)
+                {
+                    state.tmp1[i] = 0.0;
+                }
+                mincg.mincgcreate(nmain+nslack, state.tmp1, state.cgstate);
+                state.optdim = nmain+nslack;
+            }
+            
+            //
+            // Prepare transformation.
+            //
+            // MinBLEIC's handling of preconditioner matrix is somewhat unusual -
+            // instead of incorporating it into algorithm and making implicit
+            // scaling (as most optimizers do) BLEIC optimizer uses explicit
+            // scaling - it solves problem in the scaled parameters space S,
+            // making transition between scaled (S) and unscaled (X) variables
+            // every time we ask for function value.
+            //
+            // Following fields are calculated here:
+            // * TransformS         X[i] = TransformS[i]*S[i], array[NMain]
+            // * SEffective         "effective" scale of the variables after
+            //                      transformation, array[NMain+NSlack]
+            //
+            apserv.rvectorsetlengthatleast(ref state.transforms, nmain);
+            for(i=0; i<=nmain-1; i++)
+            {
+                if( state.prectype==2 )
+                {
+                    state.transforms[i] = 1/Math.Sqrt(state.diaghoriginal[i]);
+                    continue;
+                }
+                if( state.prectype==3 )
+                {
+                    state.transforms[i] = state.soriginal[i];
+                    continue;
+                }
+                state.transforms[i] = 1;
+            }
+            apserv.rvectorsetlengthatleast(ref state.seffective, nmain+nslack);
+            for(i=0; i<=nmain-1; i++)
+            {
+                state.seffective[i] = state.soriginal[i]/state.transforms[i];
+            }
+            for(i=0; i<=nslack-1; i++)
+            {
+                state.seffective[nmain+i] = 1;
+            }
+            mincg.mincgsetscale(state.cgstate, state.seffective);
+            
+            //
+            // Pre-process constraints
+            // * check consistency of bound constraints
+            // * add slack vars, convert problem to the bound/equality
+            //   constrained one
+            //
+            // We calculate here:
+            // * BndLEffective - lower bounds after transformation of variables (see above)
+            // * BndUEffective - upper bounds after transformation of variables (see above)
+            // * CEEffective - matrix of equality constraints for transformed variables
+            //
+            for(i=0; i<=nmain-1; i++)
+            {
+                if( state.hasbndl[i] )
+                {
+                    state.bndleffective[i] = state.bndloriginal[i]/state.transforms[i];
+                }
+                if( state.hasbndu[i] )
+                {
+                    state.bndueffective[i] = state.bnduoriginal[i]/state.transforms[i];
+                }
+            }
+            for(i=0; i<=nmain-1; i++)
+            {
+                if( state.hasbndl[i] && state.hasbndu[i] )
+                {
+                    if( (double)(state.bndleffective[i])>(double)(state.bndueffective[i]) )
+                    {
+                        state.repterminationtype = -3;
+                        result = false;
+                        return result;
+                    }
+                }
+            }
+            apserv.rmatrixsetlengthatleast(ref state.ceeffective, state.cecnt, nmain+nslack+1);
+            m = 0;
+            for(i=0; i<=state.cecnt-1; i++)
+            {
+                
+                //
+                // NOTE: when we add slack variable, we use V = max(abs(CE[i,...])) as
+                // coefficient before it in order to make linear equations better
+                // conditioned.
+                //
+                v = 0;
+                for(j=0; j<=nmain-1; j++)
+                {
+                    state.ceeffective[i,j] = state.ceoriginal[i,j]*state.transforms[j];
+                    v = Math.Max(v, Math.Abs(state.ceeffective[i,j]));
+                }
+                if( (double)(v)==(double)(0) )
+                {
+                    v = 1;
+                }
+                for(j=0; j<=nslack-1; j++)
+                {
+                    state.ceeffective[i,nmain+j] = 0.0;
+                }
+                state.ceeffective[i,nmain+nslack] = state.ceoriginal[i,nmain];
+                if( state.ct[i]<0 )
+                {
+                    state.ceeffective[i,nmain+m] = v;
+                    m = m+1;
+                }
+                if( state.ct[i]>0 )
+                {
+                    state.ceeffective[i,nmain+m] = -v;
+                    m = m+1;
+                }
+            }
+            
+            //
+            // Find feasible point.
+            //
+            // 0. Convert from unscaled values (as stored in XStart) to scaled
+            //    ones
+            // 1. calculate values of slack variables such that starting
+            //    point satisfies inequality constraints (after conversion to
+            //    equality ones) as much as possible.
+            // 2. use PrepareConstraintMatrix() function, which forces X
+            //    to be strictly feasible.
+            //
+            for(i=0; i<=nmain-1; i++)
+            {
+                state.tmp0[i] = state.xstart[i]/state.transforms[i];
+            }
+            m = 0;
+            for(i=0; i<=state.cecnt-1; i++)
+            {
+                v = 0.0;
+                for(i_=0; i_<=nmain-1;i_++)
+                {
+                    v += state.ceeffective[i,i_]*state.tmp0[i_];
+                }
+                if( state.ct[i]<0 )
+                {
+                    state.tmp0[nmain+m] = state.ceeffective[i,nmain+nslack]-v;
+                    m = m+1;
+                }
+                if( state.ct[i]>0 )
+                {
+                    state.tmp0[nmain+m] = v-state.ceeffective[i,nmain+nslack];
+                    m = m+1;
+                }
+            }
+            for(i=0; i<=nmain+nslack-1; i++)
+            {
+                state.tmp1[i] = 0;
+            }
+            for(i=0; i<=nmain+nslack-1; i++)
+            {
+                state.activeconstraints[i] = false;
+            }
+            b = optserv.findfeasiblepoint(ref state.tmp0, state.bndleffective, state.hasbndl, state.bndueffective, state.hasbndu, nmain, nslack, state.ceeffective, state.cecnt, state.outerepsi, ref state.repdebugfeasqpits, ref state.repdebugfeasgpaits);
+            for(i_=0; i_<=nmain+nslack-1;i_++)
+            {
+                state.xcur[i_] = state.tmp0[i_];
+            }
+            state.repdebugeqerr = 0.0;
+            for(i=0; i<=state.cecnt-1; i++)
+            {
+                v = 0.0;
+                for(i_=0; i_<=nmain+nslack-1;i_++)
+                {
+                    v += state.ceeffective[i,i_]*state.xcur[i_];
+                }
+                state.repdebugeqerr = state.repdebugeqerr+math.sqr(v-state.ceeffective[i,nmain+nslack]);
+            }
+            state.repdebugeqerr = Math.Sqrt(state.repdebugeqerr);
+            if( !b )
+            {
+                state.repterminationtype = -3;
+                result = false;
+                return result;
+            }
+            
+            //
+            // Initialize RepDebugFS with function value at initial point
+            //
+            unscalepoint(state, state.xcur, ref state.x);
+            clearrequestfields(state);
+            if( (double)(state.diffstep)!=(double)(0) )
+            {
+                goto lbl_13;
+            }
+            state.needfg = true;
+            state.rstate.stage = 0;
+            goto lbl_rcomm;
+        lbl_0:
+            state.needfg = false;
+            goto lbl_14;
+        lbl_13:
+            state.needf = true;
+            state.rstate.stage = 1;
+            goto lbl_rcomm;
+        lbl_1:
+            state.needf = false;
+        lbl_14:
+            optserv.trimprepare(state.f, ref state.trimthreshold);
+            state.repnfev = state.repnfev+1;
+            state.repdebugfs = state.f;
+            
+            //
+            // Outer cycle
+            //
+            state.itsleft = state.maxits;
+            for(i_=0; i_<=nmain+nslack-1;i_++)
+            {
+                state.xprev[i_] = state.xcur[i_];
+            }
+        lbl_15:
+            if( false )
+            {
+                goto lbl_16;
+            }
+            alglib.ap.assert(state.prectype==0 || (double)(state.stpmax)==(double)(0), "MinBLEIC: internal error (-10)");
+            
+            //
+            // Inner cycle: CG with projections and penalty functions
+            //
+            for(i_=0; i_<=nmain+nslack-1;i_++)
+            {
+                state.tmp0[i_] = state.xcur[i_];
+            }
+            for(i=0; i<=nmain+nslack-1; i++)
+            {
+                state.tmp1[i] = 0;
+                state.activeconstraints[i] = false;
+            }
+            if( !prepareconstraintmatrix(state, state.tmp0, state.tmp1, ref state.xcur, ref state.tmp2) )
+            {
+                state.repterminationtype = -3;
+                result = false;
+                return result;
+            }
+            for(i=0; i<=nmain+nslack-1; i++)
+            {
+                state.activeconstraints[i] = false;
+            }
+            rebuildcexe(state);
+            mincg.mincgrestartfrom(state.cgstate, state.xcur);
+            mincg.mincgsetcond(state.cgstate, state.innerepsg, state.innerepsf, state.innerepsx, state.itsleft);
+            mincg.mincgsetxrep(state.cgstate, state.xrep);
+            mincg.mincgsetdrep(state.cgstate, true);
+            mincg.mincgsetstpmax(state.cgstate, state.stpmax);
+        lbl_17:
+            if( !mincg.mincgiteration(state.cgstate) )
+            {
+                goto lbl_18;
+            }
+            
+            //
+            // process different requests/reports of inner optimizer
+            //
+            if( state.cgstate.algpowerup )
+            {
+                for(i=0; i<=nmain+nslack-1; i++)
+                {
+                    state.activeconstraints[i] = false;
+                }
+                do
+                {
+                    rebuildcexe(state);
+                    for(i_=0; i_<=nmain+nslack-1;i_++)
+                    {
+                        state.tmp1[i_] = state.cgstate.g[i_];
+                    }
+                    makegradientprojection(state, ref state.tmp1);
+                    b = false;
+                    for(i=0; i<=nmain-1; i++)
+                    {
+                        if( !state.activeconstraints[i] )
+                        {
+                            if( state.hasbndl[i] )
+                            {
+                                if( (double)(state.cgstate.x[i])==(double)(state.bndleffective[i]) && (double)(state.tmp1[i])>=(double)(0) )
+                                {
+                                    state.activeconstraints[i] = true;
+                                    state.constrainedvalues[i] = state.bndleffective[i];
+                                    b = true;
+                                }
+                            }
+                            if( state.hasbndu[i] )
+                            {
+                                if( (double)(state.cgstate.x[i])==(double)(state.bndueffective[i]) && (double)(state.tmp1[i])<=(double)(0) )
+                                {
+                                    state.activeconstraints[i] = true;
+                                    state.constrainedvalues[i] = state.bndueffective[i];
+                                    b = true;
+                                }
+                            }
+                        }
+                    }
+                    for(i=0; i<=nslack-1; i++)
+                    {
+                        if( !state.activeconstraints[nmain+i] )
+                        {
+                            if( (double)(state.cgstate.x[nmain+i])==(double)(0) && (double)(state.tmp1[nmain+i])>=(double)(0) )
+                            {
+                                state.activeconstraints[nmain+i] = true;
+                                state.constrainedvalues[nmain+i] = 0;
+                                b = true;
+                            }
+                        }
+                    }
+                }
+                while( b );
+                for(i_=0; i_<=nmain+nslack-1;i_++)
+                {
+                    state.cgstate.g[i_] = state.tmp1[i_];
+                }
+                goto lbl_17;
+            }
+            if( state.cgstate.lsstart )
+            {
+                
+                //
+                // Beginning of the line search: set upper limit on step size
+                // to prevent algo from leaving feasible area.
+                //
+                if( (double)(state.cgstate.curstpmax)==(double)(0) )
+                {
+                    state.cgstate.curstpmax = 1.0E50;
+                }
+                state.variabletofreeze = -1;
+                for(i=0; i<=nmain-1; i++)
+                {
+                    if( state.hasbndl[i] && (double)(state.cgstate.d[i])<(double)(0) )
+                    {
+                        v = state.cgstate.curstpmax;
+                        vv = state.cgstate.x[i]-state.bndleffective[i];
+                        if( (double)(vv)<(double)(0) )
+                        {
+                            vv = 0;
+                        }
+                        state.cgstate.curstpmax = apserv.safeminposrv(vv, -state.cgstate.d[i], state.cgstate.curstpmax);
+                        if( (double)(state.cgstate.curstpmax)<(double)(v) )
+                        {
+                            state.variabletofreeze = i;
+                            state.valuetofreeze = state.bndleffective[i];
+                        }
+                    }
+                    if( state.hasbndu[i] && (double)(state.cgstate.d[i])>(double)(0) )
+                    {
+                        v = state.cgstate.curstpmax;
+                        vv = state.bndueffective[i]-state.cgstate.x[i];
+                        if( (double)(vv)<(double)(0) )
+                        {
+                            vv = 0;
+                        }
+                        state.cgstate.curstpmax = apserv.safeminposrv(vv, state.cgstate.d[i], state.cgstate.curstpmax);
+                        if( (double)(state.cgstate.curstpmax)<(double)(v) )
+                        {
+                            state.variabletofreeze = i;
+                            state.valuetofreeze = state.bndueffective[i];
+                        }
+                    }
+                }
+                for(i=0; i<=nslack-1; i++)
+                {
+                    if( (double)(state.cgstate.d[nmain+i])<(double)(0) )
+                    {
+                        v = state.cgstate.curstpmax;
+                        vv = state.cgstate.x[nmain+i];
+                        if( (double)(vv)<(double)(0) )
+                        {
+                            vv = 0;
+                        }
+                        state.cgstate.curstpmax = apserv.safeminposrv(vv, -state.cgstate.d[nmain+i], state.cgstate.curstpmax);
+                        if( (double)(state.cgstate.curstpmax)<(double)(v) )
+                        {
+                            state.variabletofreeze = nmain+i;
+                            state.valuetofreeze = 0;
+                        }
+                    }
+                }
+                if( (double)(state.cgstate.curstpmax)==(double)(0) )
+                {
+                    state.activeconstraints[state.variabletofreeze] = true;
+                    state.constrainedvalues[state.variabletofreeze] = state.valuetofreeze;
+                    state.cgstate.x[state.variabletofreeze] = state.valuetofreeze;
+                    state.cgstate.terminationneeded = true;
+                }
+                goto lbl_17;
+            }
+            if( state.cgstate.lsend )
+            {
+                
+                //
+                // Line search just finished.
+                // Maybe we should activate some constraints?
+                //
+                if( state.variabletofreeze>=0 )
+                {
+                    
+                    //
+                    // We have candidate constraint for activation, which can be
+                    // activated in two cases:
+                    //
+                    // 1. monotonic step with Stp=StpMax is performed (MCINFO=5),
+                    //    we just have to post-process this step to ensure that
+                    //    constraint is activated
+                    //
+                    // 2. MCINFO<>5 and MCINFO<>1, and BOTH of the following holds:
+                    //    2.a) ScaledNorm(State.CGState.G)>InnerEpsG (strict inequality to handle EpsG=0)
+                    //    2.b) ScaledNorm(StpMax*State.CGState.D)<=MaxNonMonotonicLen*MachineEpsilon;
+                    //
+                    //    What do these conditions mean?
+                    //    a) Having MCINFO which is not 5 or 1 means that it is
+                    //       possible that our function is actually increasing in
+                    //       the direction of D instead of decreasing.
+                    //    b) clause (2.a) means that gradient value is large enough
+                    //       to consider it non-zero (EpsG=0 means that any non-zero
+                    //       gradient value will be large enough).
+                    //    c) clause (2.b) means that step length is very small and
+                    //       function non-monotonicity can be just a consequence of
+                    //       numerical noise.
+                    //
+                    //    Given all of the listed above, it may be worth trying to
+                    //    perform step which increases function value.
+                    //
+                    // NOTE: some non-monotonic algorithms can be caught in an eternal
+                    //       loop. However, this algorithm is guaranteed from having
+                    //       such eternal loop because non-monotonic step always activates
+                    //       at least one constraint, and in order to have eternal loop
+                    //       we need to deactivate this constraint, which can be done
+                    //       only at the beginning of the outer iteration. And we have
+                    //       upper limit on the number of outer iterations, so we can
+                    //       be sure that algorithm will stop eventually.
+                    //
+                    if( state.cgstate.mcinfo!=5 && state.cgstate.mcinfo!=1 )
+                    {
+                        v = 0;
+                        vv = 0;
+                        for(i=0; i<=nmain+nslack-1; i++)
+                        {
+                            v = v+math.sqr(state.cgstate.g[i]*state.seffective[i]);
+                            vv = vv+math.sqr(state.cgstate.curstpmax*state.cgstate.d[i]/state.seffective[i]);
+                        }
+                        v = Math.Sqrt(v);
+                        vv = Math.Sqrt(vv);
+                        b = (double)(v)>(double)(state.innerepsg) && (double)(vv)<=(double)(maxnonmonotoniclen*math.machineepsilon);
+                    }
+                    else
+                    {
+                        b = state.cgstate.mcinfo==5;
+                    }
+                    if( b )
+                    {
+                        state.activeconstraints[state.variabletofreeze] = true;
+                        state.constrainedvalues[state.variabletofreeze] = state.valuetofreeze;
+                    }
+                }
+                else
+                {
+                    b = false;
+                }
+                
+                //
+                // Additional activation of constraints
+                //
+                b = b || additionalcheckforconstraints(state, state.cgstate.x);
+                
+                //
+                // If at least one constraint was activated we have to rebuild constraint matrices
+                //
+                if( b )
+                {
+                    for(i_=0; i_<=nmain+nslack-1;i_++)
+                    {
+                        state.tmp0[i_] = state.cgstate.x[i_];
+                    }
+                    for(i_=0; i_<=nmain+nslack-1;i_++)
+                    {
+                        state.tmp1[i_] = state.lastg[i_];
+                    }
+                    if( !prepareconstraintmatrix(state, state.tmp0, state.tmp1, ref state.cgstate.x, ref state.cgstate.g) )
+                    {
+                        state.repterminationtype = -3;
+                        result = false;
+                        return result;
+                    }
+                    state.cgstate.innerresetneeded = true;
+                }
+                goto lbl_17;
+            }
+            if( !state.cgstate.needfg )
+            {
+                goto lbl_19;
+            }
+            for(i_=0; i_<=nmain+nslack-1;i_++)
+            {
+                state.tmp1[i_] = state.cgstate.x[i_];
+            }
+            projectpointandunscale(state, ref state.tmp1, ref state.x, ref state.r, ref vv);
+            clearrequestfields(state);
+            if( (double)(state.diffstep)!=(double)(0) )
+            {
+                goto lbl_21;
+            }
+            state.needfg = true;
+            state.rstate.stage = 2;
+            goto lbl_rcomm;
+        lbl_2:
+            state.needfg = false;
+            goto lbl_22;
+        lbl_21:
+            state.needf = true;
+            state.rstate.stage = 3;
+            goto lbl_rcomm;
+        lbl_3:
+            state.fbase = state.f;
+            i = 0;
+        lbl_23:
+            if( i>nmain-1 )
+            {
+                goto lbl_25;
+            }
+            v = state.x[i];
+            b = false;
+            if( state.hasbndl[i] )
+            {
+                b = b || (double)(v-state.diffstep*state.soriginal[i])<(double)(state.bndloriginal[i]);
+            }
+            if( state.hasbndu[i] )
+            {
+                b = b || (double)(v+state.diffstep*state.soriginal[i])>(double)(state.bnduoriginal[i]);
+            }
+            if( b )
+            {
+                goto lbl_26;
+            }
+            state.x[i] = v-state.diffstep*state.soriginal[i];
+            state.rstate.stage = 4;
+            goto lbl_rcomm;
+        lbl_4:
+            state.fm2 = state.f;
+            state.x[i] = v-0.5*state.diffstep*state.soriginal[i];
+            state.rstate.stage = 5;
+            goto lbl_rcomm;
+        lbl_5:
+            state.fm1 = state.f;
+            state.x[i] = v+0.5*state.diffstep*state.soriginal[i];
+            state.rstate.stage = 6;
+            goto lbl_rcomm;
+        lbl_6:
+            state.fp1 = state.f;
+            state.x[i] = v+state.diffstep*state.soriginal[i];
+            state.rstate.stage = 7;
+            goto lbl_rcomm;
+        lbl_7:
+            state.fp2 = state.f;
+            state.g[i] = (8*(state.fp1-state.fm1)-(state.fp2-state.fm2))/(6*state.diffstep*state.soriginal[i]);
+            goto lbl_27;
+        lbl_26:
+            state.xm1 = Math.Max(v-state.diffstep*state.soriginal[i], state.bndloriginal[i]);
+            state.x[i] = state.xm1;
+            state.rstate.stage = 8;
+            goto lbl_rcomm;
+        lbl_8:
+            state.fm1 = state.f;
+            state.xp1 = Math.Min(v+state.diffstep*state.soriginal[i], state.bnduoriginal[i]);
+            state.x[i] = state.xp1;
+            state.rstate.stage = 9;
+            goto lbl_rcomm;
+        lbl_9:
+            state.fp1 = state.f;
+            state.g[i] = (state.fp1-state.fm1)/(state.xp1-state.xm1);
+        lbl_27:
+            state.x[i] = v;
+            i = i+1;
+            goto lbl_23;
+        lbl_25:
+            state.f = state.fbase;
+            state.needf = false;
+        lbl_22:
+            if( (double)(state.f)<(double)(state.trimthreshold) )
+            {
+                
+                //
+                // normal processing
+                //
+                state.cgstate.f = state.f;
+                scalegradientandexpand(state, state.g, ref state.cgstate.g);
+                for(i_=0; i_<=nmain+nslack-1;i_++)
+                {
+                    state.lastg[i_] = state.cgstate.g[i_];
+                }
+                modifytargetfunction(state, state.tmp1, state.r, vv, ref state.cgstate.f, ref state.cgstate.g, ref state.gnorm, ref state.mpgnorm);
+            }
+            else
+            {
+                
+                //
+                // function value is too high, trim it
+                //
+                state.cgstate.f = state.trimthreshold;
+                for(i=0; i<=nmain+nslack-1; i++)
+                {
+                    state.cgstate.g[i] = 0.0;
+                }
+            }
+            goto lbl_17;
+        lbl_19:
+            if( !state.cgstate.xupdated )
+            {
+                goto lbl_28;
+            }
+            
+            //
+            // Report
+            //
+            unscalepoint(state, state.cgstate.x, ref state.x);
+            state.f = state.cgstate.f;
+            clearrequestfields(state);
+            state.xupdated = true;
+            state.rstate.stage = 10;
+            goto lbl_rcomm;
+        lbl_10:
+            state.xupdated = false;
+            goto lbl_17;
+        lbl_28:
+            goto lbl_17;
+        lbl_18:
+            mincg.mincgresults(state.cgstate, ref state.xcur, state.cgrep);
+            unscalepoint(state, state.xcur, ref state.xend);
+            state.repinneriterationscount = state.repinneriterationscount+state.cgrep.iterationscount;
+            state.repouteriterationscount = state.repouteriterationscount+1;
+            state.repnfev = state.repnfev+state.cgrep.nfev;
+            
+            //
+            // Update RepDebugFF with function value at current point
+            //
+            unscalepoint(state, state.xcur, ref state.x);
+            clearrequestfields(state);
+            if( (double)(state.diffstep)!=(double)(0) )
+            {
+                goto lbl_30;
+            }
+            state.needfg = true;
+            state.rstate.stage = 11;
+            goto lbl_rcomm;
+        lbl_11:
+            state.needfg = false;
+            goto lbl_31;
+        lbl_30:
+            state.needf = true;
+            state.rstate.stage = 12;
+            goto lbl_rcomm;
+        lbl_12:
+            state.needf = false;
+        lbl_31:
+            state.repnfev = state.repnfev+1;
+            state.repdebugff = state.f;
+            
+            //
+            // Check for stopping:
+            // * "normal", outer step size is small enough, infeasibility is within bounds
+            // * "inconsistent",  if Lagrange multipliers increased beyond threshold given by MaxLagrangeMul
+            // * "too stringent", in other cases
+            //
+            v = 0;
+            for(i=0; i<=nmain-1; i++)
+            {
+                v = v+math.sqr((state.xcur[i]-state.xprev[i])/state.seffective[i]);
+            }
+            v = Math.Sqrt(v);
+            if( (double)(v)<=(double)(state.outerepsx) )
+            {
+                state.repterminationtype = 4;
+                goto lbl_16;
+            }
+            if( state.maxits>0 )
+            {
+                state.itsleft = state.itsleft-state.cgrep.iterationscount;
+                if( state.itsleft<=0 )
+                {
+                    state.repterminationtype = 5;
+                    goto lbl_16;
+                }
+            }
+            if( (double)(state.repouteriterationscount)>=(double)(maxouterits) )
+            {
+                
+                //
+                // Stopped because of reaching limit on the number of outer iterations.
+                //
+                // IMPORTANT: DO NOT REMOVE THIS STOPPING CRITERIA because it
+                //            saves algorithm from eternal loop with non-monotonic
+                //            steps being made (first step increases function value,
+                //            next step moves us back). See comments on the code which
+                //            handles State.CGState.LSEnd for more info on this subject.
+                //
+                state.repterminationtype = 5;
+                goto lbl_16;
+            }
+            
+            //
+            // Next iteration
+            //
+            for(i_=0; i_<=nmain+nslack-1;i_++)
+            {
+                state.xprev[i_] = state.xcur[i_];
+            }
+            goto lbl_15;
+        lbl_16:
+            
+            //
+            // We've stopped, fill debug information
+            //
+            state.repdebugeqerr = 0.0;
+            for(i=0; i<=state.cecnt-1; i++)
+            {
+                v = 0.0;
+                for(i_=0; i_<=nmain+nslack-1;i_++)
+                {
+                    v += state.ceeffective[i,i_]*state.xcur[i_];
+                }
+                state.repdebugeqerr = state.repdebugeqerr+math.sqr(v-state.ceeffective[i,nmain+nslack]);
+            }
+            state.repdebugeqerr = Math.Sqrt(state.repdebugeqerr);
+            state.repdebugdx = 0;
+            for(i=0; i<=nmain-1; i++)
+            {
+                state.repdebugdx = state.repdebugdx+math.sqr(state.xcur[i]-state.xstart[i]);
+            }
+            state.repdebugdx = Math.Sqrt(state.repdebugdx);
+            result = false;
+            return result;
+            
+            //
+            // Saving state
+            //
+        lbl_rcomm:
+            result = true;
+            state.rstate.ia[0] = nmain;
+            state.rstate.ia[1] = nslack;
+            state.rstate.ia[2] = m;
+            state.rstate.ia[3] = i;
+            state.rstate.ia[4] = j;
+            state.rstate.ba[0] = b;
+            state.rstate.ra[0] = v;
+            state.rstate.ra[1] = vv;
+            return result;
+        }
+
+
+        /*************************************************************************
+        BLEIC results
+
+        INPUT PARAMETERS:
+            State   -   algorithm state
+
+        OUTPUT PARAMETERS:
+            X       -   array[0..N-1], solution
+            Rep     -   optimization report. You should check Rep.TerminationType
+                        in  order  to  distinguish  successful  termination  from
+                        unsuccessful one.
+                        More information about fields of this  structure  can  be
+                        found in the comments on MinBLEICReport datatype.
+
+          -- ALGLIB --
+             Copyright 28.11.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minbleicresults(minbleicstate state,
+            ref double[] x,
+            minbleicreport rep)
+        {
+            x = new double[0];
+
+            minbleicresultsbuf(state, ref x, rep);
+        }
+
+
+        /*************************************************************************
+        BLEIC results
+
+        Buffered implementation of MinBLEICResults() which uses pre-allocated buffer
+        to store X[]. If buffer size is  too  small,  it  resizes  buffer.  It  is
+        intended to be used in the inner cycles of performance critical algorithms
+        where array reallocation penalty is too large to be ignored.
+
+          -- ALGLIB --
+             Copyright 28.11.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minbleicresultsbuf(minbleicstate state,
+            ref double[] x,
+            minbleicreport rep)
+        {
+            int i = 0;
+            int i_ = 0;
+
+            if( alglib.ap.len(x)<state.nmain )
+            {
+                x = new double[state.nmain];
+            }
+            rep.inneriterationscount = state.repinneriterationscount;
+            rep.outeriterationscount = state.repouteriterationscount;
+            rep.nfev = state.repnfev;
+            rep.terminationtype = state.repterminationtype;
+            if( state.repterminationtype>0 )
+            {
+                for(i_=0; i_<=state.nmain-1;i_++)
+                {
+                    x[i_] = state.xend[i_];
+                }
+            }
+            else
+            {
+                for(i=0; i<=state.nmain-1; i++)
+                {
+                    x[i] = Double.NaN;
+                }
+            }
+            rep.debugeqerr = state.repdebugeqerr;
+            rep.debugfs = state.repdebugfs;
+            rep.debugff = state.repdebugff;
+            rep.debugdx = state.repdebugdx;
+            rep.debugfeasqpits = state.repdebugfeasqpits;
+            rep.debugfeasgpaits = state.repdebugfeasgpaits;
+        }
+
+
+        /*************************************************************************
+        This subroutine restarts algorithm from new point.
+        All optimization parameters (including constraints) are left unchanged.
+
+        This  function  allows  to  solve multiple  optimization  problems  (which
+        must have  same number of dimensions) without object reallocation penalty.
+
+        INPUT PARAMETERS:
+            State   -   structure previously allocated with MinBLEICCreate call.
+            X       -   new starting point.
+
+          -- ALGLIB --
+             Copyright 28.11.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minbleicrestartfrom(minbleicstate state,
+            double[] x)
+        {
+            int n = 0;
+            int i_ = 0;
+
+            n = state.nmain;
+            
+            //
+            // First, check for errors in the inputs
+            //
+            alglib.ap.assert(alglib.ap.len(x)>=n, "MinBLEICRestartFrom: Length(X)<N");
+            alglib.ap.assert(apserv.isfinitevector(x, n), "MinBLEICRestartFrom: X contains infinite or NaN values!");
+            
+            //
+            // Set XC
+            //
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.xstart[i_] = x[i_];
+            }
+            
+            //
+            // prepare RComm facilities
+            //
+            state.rstate.ia = new int[4+1];
+            state.rstate.ba = new bool[0+1];
+            state.rstate.ra = new double[1+1];
+            state.rstate.stage = -1;
+            clearrequestfields(state);
+        }
+
+
+        /*************************************************************************
+        Clears request fileds (to be sure that we don't forget to clear something)
+        *************************************************************************/
+        private static void clearrequestfields(minbleicstate state)
+        {
+            state.needf = false;
+            state.needfg = false;
+            state.xupdated = false;
+        }
+
+
+        /*************************************************************************
+        This functions "unscales" point, i.e. it makes transformation  from scaled
+        variables to unscaled ones. Only leading NMain variables are copied from
+        XUnscaled to XScaled.
+        *************************************************************************/
+        private static void unscalepoint(minbleicstate state,
+            double[] xscaled,
+            ref double[] xunscaled)
+        {
+            int i = 0;
+            double v = 0;
+
+            for(i=0; i<=state.nmain-1; i++)
+            {
+                v = xscaled[i]*state.transforms[i];
+                if( state.hasbndl[i] )
+                {
+                    if( (double)(v)<(double)(state.bndloriginal[i]) )
+                    {
+                        v = state.bndloriginal[i];
+                    }
+                }
+                if( state.hasbndu[i] )
+                {
+                    if( (double)(v)>(double)(state.bnduoriginal[i]) )
+                    {
+                        v = state.bnduoriginal[i];
+                    }
+                }
+                xunscaled[i] = v;
+            }
+        }
+
+
+        /*************************************************************************
+        This function:
+        1. makes projection of XScaled into equality constrained subspace
+           (X is modified in-place)
+        2. stores residual from the projection into R
+        3. unscales projected XScaled and stores result into XUnscaled with
+           additional enforcement
+        It calculates set of additional values which are used later for
+        modification of the target function F.
+
+        INPUT PARAMETERS:
+            State   -   optimizer state (we use its fields to get information
+                        about constraints)
+            X       -   vector being projected
+            R       -   preallocated buffer, used to store residual from projection
+
+        OUTPUT PARAMETERS:
+            X       -   projection of input X
+            R       -   residual
+            RNorm   -   residual norm squared, used later to modify target function
+        *************************************************************************/
+        private static void projectpointandunscale(minbleicstate state,
+            ref double[] xscaled,
+            ref double[] xunscaled,
+            ref double[] rscaled,
+            ref double rnorm2)
+        {
+            double v = 0;
+            int i = 0;
+            int nmain = 0;
+            int nslack = 0;
+            int i_ = 0;
+
+            rnorm2 = 0;
+
+            nmain = state.nmain;
+            nslack = state.nslack;
+            
+            //
+            // * subtract XE from XScaled
+            // * project XScaled
+            // * calculate norm of deviation from null space, store it in RNorm2
+            // * calculate residual from projection, store it in R
+            // * add XE to XScaled
+            // * unscale variables
+            //
+            for(i_=0; i_<=nmain+nslack-1;i_++)
+            {
+                xscaled[i_] = xscaled[i_] - state.xe[i_];
+            }
+            rnorm2 = 0;
+            for(i=0; i<=nmain+nslack-1; i++)
+            {
+                rscaled[i] = 0;
+            }
+            for(i=0; i<=nmain+nslack-1; i++)
+            {
+                if( state.activeconstraints[i] )
+                {
+                    v = xscaled[i];
+                    xscaled[i] = 0;
+                    rscaled[i] = rscaled[i]+v;
+                    rnorm2 = rnorm2+math.sqr(v);
+                }
+            }
+            for(i=0; i<=state.cecnt-1; i++)
+            {
+                v = 0.0;
+                for(i_=0; i_<=nmain+nslack-1;i_++)
+                {
+                    v += xscaled[i_]*state.cecurrent[i,i_];
+                }
+                for(i_=0; i_<=nmain+nslack-1;i_++)
+                {
+                    xscaled[i_] = xscaled[i_] - v*state.cecurrent[i,i_];
+                }
+                for(i_=0; i_<=nmain+nslack-1;i_++)
+                {
+                    rscaled[i_] = rscaled[i_] + v*state.cecurrent[i,i_];
+                }
+                rnorm2 = rnorm2+math.sqr(v);
+            }
+            for(i_=0; i_<=nmain+nslack-1;i_++)
+            {
+                xscaled[i_] = xscaled[i_] + state.xe[i_];
+            }
+            unscalepoint(state, xscaled, ref xunscaled);
+        }
+
+
+        /*************************************************************************
+        This function scales and copies NMain elements of GUnscaled into GScaled.
+        Other NSlack components of GScaled are set to zero.
+        *************************************************************************/
+        private static void scalegradientandexpand(minbleicstate state,
+            double[] gunscaled,
+            ref double[] gscaled)
+        {
+            int i = 0;
+
+            for(i=0; i<=state.nmain-1; i++)
+            {
+                gscaled[i] = gunscaled[i]*state.transforms[i];
+            }
+            for(i=0; i<=state.nslack-1; i++)
+            {
+                gscaled[state.nmain+i] = 0;
+            }
+        }
+
+
+        /*************************************************************************
+        This subroutine applies modifications to the target function given by
+        its value F and gradient G at the projected point X which lies in the
+        equality constrained subspace.
+
+        Following modifications are applied:
+        * modified barrier functions to handle inequality constraints
+          (both F and G are modified)
+        * projection of gradient into equality constrained subspace
+          (only G is modified)
+        * quadratic penalty for deviations from equality constrained subspace
+          (both F and G are modified)
+
+        It also calculates gradient norm (three different norms for three
+        different types of gradient), feasibility and complementary slackness
+        errors.
+
+        INPUT PARAMETERS:
+            State   -   optimizer state (we use its fields to get information
+                        about constraints)
+            X       -   point (projected into equality constrained subspace)
+            R       -   residual from projection
+            RNorm2  -   residual norm squared
+            F       -   function value at X
+            G       -   function gradient at X
+
+        OUTPUT PARAMETERS:
+            F       -   modified function value at X
+            G       -   modified function gradient at X
+            GNorm   -   2-norm of unmodified G
+            MPGNorm -   2-norm of modified G
+            MBA     -   minimum argument of barrier functions.
+                        If X is strictly feasible, it is greater than zero.
+                        If X lies on a boundary, it is zero.
+                        It is negative for infeasible X.
+            FIErr   -   2-norm of feasibility error with respect to
+                        inequality/bound constraints
+            CSErr   -   2-norm of complementarity slackness error
+        *************************************************************************/
+        private static void modifytargetfunction(minbleicstate state,
+            double[] x,
+            double[] r,
+            double rnorm2,
+            ref double f,
+            ref double[] g,
+            ref double gnorm,
+            ref double mpgnorm)
+        {
+            double v = 0;
+            int i = 0;
+            int nmain = 0;
+            int nslack = 0;
+            bool hasconstraints = new bool();
+            int i_ = 0;
+
+            gnorm = 0;
+            mpgnorm = 0;
+
+            nmain = state.nmain;
+            nslack = state.nslack;
+            hasconstraints = false;
+            
+            //
+            // GNorm
+            //
+            v = 0.0;
+            for(i_=0; i_<=nmain+nslack-1;i_++)
+            {
+                v += g[i_]*g[i_];
+            }
+            gnorm = Math.Sqrt(v);
+            
+            //
+            // Process equality constraints:
+            // * modify F to handle penalty term for equality constraints
+            // * project gradient on null space of equality constraints
+            // * add penalty term for equality constraints to gradient
+            //
+            f = f+rnorm2;
+            for(i=0; i<=nmain+nslack-1; i++)
+            {
+                if( state.activeconstraints[i] )
+                {
+                    g[i] = 0;
+                }
+            }
+            for(i=0; i<=state.cecnt-1; i++)
+            {
+                v = 0.0;
+                for(i_=0; i_<=nmain+nslack-1;i_++)
+                {
+                    v += g[i_]*state.cecurrent[i,i_];
+                }
+                for(i_=0; i_<=nmain+nslack-1;i_++)
+                {
+                    g[i_] = g[i_] - v*state.cecurrent[i,i_];
+                }
+            }
+            for(i_=0; i_<=nmain+nslack-1;i_++)
+            {
+                g[i_] = g[i_] + 2*r[i_];
+            }
+            
+            //
+            // MPGNorm
+            //
+            v = 0.0;
+            for(i_=0; i_<=nmain+nslack-1;i_++)
+            {
+                v += g[i_]*g[i_];
+            }
+            mpgnorm = Math.Sqrt(v);
+        }
+
+
+        /*************************************************************************
+        This function makes additional check for constraints which can be activated.
+
+        We try activate constraints one by one, but it is possible that several
+        constraints should be activated during one iteration. It this case only
+        one of them (probably last) will be activated. This function will fix it -
+        it will pass through constraints and activate those which are at the boundary
+        or beyond it.
+
+        It will return True, if at least one constraint was activated by this function.
+        *************************************************************************/
+        private static bool additionalcheckforconstraints(minbleicstate state,
+            double[] x)
+        {
+            bool result = new bool();
+            int i = 0;
+            int nmain = 0;
+            int nslack = 0;
+
+            result = false;
+            nmain = state.nmain;
+            nslack = state.nslack;
+            for(i=0; i<=nmain-1; i++)
+            {
+                if( !state.activeconstraints[i] )
+                {
+                    if( state.hasbndl[i] )
+                    {
+                        if( (double)(x[i])<=(double)(state.bndleffective[i]) )
+                        {
+                            state.activeconstraints[i] = true;
+                            state.constrainedvalues[i] = state.bndleffective[i];
+                            result = true;
+                        }
+                    }
+                    if( state.hasbndu[i] )
+                    {
+                        if( (double)(x[i])>=(double)(state.bndueffective[i]) )
+                        {
+                            state.activeconstraints[i] = true;
+                            state.constrainedvalues[i] = state.bndueffective[i];
+                            result = true;
+                        }
+                    }
+                }
+            }
+            for(i=0; i<=nslack-1; i++)
+            {
+                if( !state.activeconstraints[nmain+i] )
+                {
+                    if( (double)(x[nmain+i])<=(double)(0) )
+                    {
+                        state.activeconstraints[nmain+i] = true;
+                        state.constrainedvalues[nmain+i] = 0;
+                        result = true;
+                    }
+                }
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function rebuilds CECurrent and XE according to current set of
+        active bound constraints.
+        *************************************************************************/
+        private static void rebuildcexe(minbleicstate state)
+        {
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int nmain = 0;
+            int nslack = 0;
+            double v = 0;
+            int i_ = 0;
+
+            nmain = state.nmain;
+            nslack = state.nslack;
+            ablas.rmatrixcopy(state.cecnt, nmain+nslack+1, state.ceeffective, 0, 0, ref state.cecurrent, 0, 0);
+            for(i=0; i<=state.cecnt-1; i++)
+            {
+                
+                //
+                // "Subtract" active bound constraints from I-th linear constraint
+                //
+                for(j=0; j<=nmain+nslack-1; j++)
+                {
+                    if( state.activeconstraints[j] )
+                    {
+                        state.cecurrent[i,nmain+nslack] = state.cecurrent[i,nmain+nslack]-state.cecurrent[i,j]*state.constrainedvalues[j];
+                        state.cecurrent[i,j] = 0.0;
+                    }
+                }
+                
+                //
+                // Reorthogonalize I-th constraint with respect to previous ones
+                // NOTE: we also update right part, which is CECurrent[...,NMain+NSlack].
+                //
+                for(k=0; k<=i-1; k++)
+                {
+                    v = 0.0;
+                    for(i_=0; i_<=nmain+nslack-1;i_++)
+                    {
+                        v += state.cecurrent[k,i_]*state.cecurrent[i,i_];
+                    }
+                    for(i_=0; i_<=nmain+nslack;i_++)
+                    {
+                        state.cecurrent[i,i_] = state.cecurrent[i,i_] - v*state.cecurrent[k,i_];
+                    }
+                }
+                
+                //
+                // Calculate norm of I-th row of CECurrent. Fill by zeros, if it is
+                // too small. Normalize otherwise.
+                //
+                // NOTE: we also scale last column of CECurrent (right part)
+                //
+                v = 0.0;
+                for(i_=0; i_<=nmain+nslack-1;i_++)
+                {
+                    v += state.cecurrent[i,i_]*state.cecurrent[i,i_];
+                }
+                v = Math.Sqrt(v);
+                if( (double)(v)>(double)(10000*math.machineepsilon) )
+                {
+                    v = 1/v;
+                    for(i_=0; i_<=nmain+nslack;i_++)
+                    {
+                        state.cecurrent[i,i_] = v*state.cecurrent[i,i_];
+                    }
+                }
+                else
+                {
+                    for(j=0; j<=nmain+nslack; j++)
+                    {
+                        state.cecurrent[i,j] = 0;
+                    }
+                }
+            }
+            for(j=0; j<=nmain+nslack-1; j++)
+            {
+                state.xe[j] = 0;
+            }
+            for(i=0; i<=nmain+nslack-1; i++)
+            {
+                if( state.activeconstraints[i] )
+                {
+                    state.xe[i] = state.xe[i]+state.constrainedvalues[i];
+                }
+            }
+            for(i=0; i<=state.cecnt-1; i++)
+            {
+                v = state.cecurrent[i,nmain+nslack];
+                for(i_=0; i_<=nmain+nslack-1;i_++)
+                {
+                    state.xe[i_] = state.xe[i_] + v*state.cecurrent[i,i_];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This function projects gradient onto equality constrained subspace
+        *************************************************************************/
+        private static void makegradientprojection(minbleicstate state,
+            ref double[] pg)
+        {
+            int i = 0;
+            int nmain = 0;
+            int nslack = 0;
+            double v = 0;
+            int i_ = 0;
+
+            nmain = state.nmain;
+            nslack = state.nslack;
+            for(i=0; i<=nmain+nslack-1; i++)
+            {
+                if( state.activeconstraints[i] )
+                {
+                    pg[i] = 0;
+                }
+            }
+            for(i=0; i<=state.cecnt-1; i++)
+            {
+                v = 0.0;
+                for(i_=0; i_<=nmain+nslack-1;i_++)
+                {
+                    v += pg[i_]*state.cecurrent[i,i_];
+                }
+                for(i_=0; i_<=nmain+nslack-1;i_++)
+                {
+                    pg[i_] = pg[i_] - v*state.cecurrent[i,i_];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This function prepares equality constrained subproblem:
+
+        1. X is used to activate constraints (if there are ones which are still
+           inactive, but should be activated).
+        2. constraints matrix CEOrt is copied to CECurrent and modified  according
+           to the list of active bound constraints (corresponding elements are
+           filled by zeros and reorthogonalized).
+        3. XE - least squares solution of equality constraints - is recalculated
+        4. X is copied to PX and projected onto equality constrained subspace
+        5. inactive constraints are checked against PX - if there is at least one
+           which should be activated, we activate it and move back to (2)
+        6. as result, PX is feasible with respect to bound constraints - step (5)
+           guarantees it. But PX can be infeasible with respect to equality ones,
+           because step (2) is done without checks for consistency. As the final
+           step, we check that PX is feasible. If not, we return False. True is
+           returned otherwise.
+
+        If this algorithm returned True, then:
+        * X is not changed
+        * PX contains projection of X onto constrained subspace
+        * G is not changed
+        * PG contains projection of G onto constrained subspace
+        * PX is feasible with respect to all constraints
+        * all constraints which are active at PX, are activated
+        *************************************************************************/
+        private static bool prepareconstraintmatrix(minbleicstate state,
+            double[] x,
+            double[] g,
+            ref double[] px,
+            ref double[] pg)
+        {
+            bool result = new bool();
+            int i = 0;
+            int nmain = 0;
+            int nslack = 0;
+            double v = 0;
+            double ferr = 0;
+            int i_ = 0;
+
+            nmain = state.nmain;
+            nslack = state.nslack;
+            result = true;
+            
+            //
+            // Step 1
+            //
+            additionalcheckforconstraints(state, x);
+            
+            //
+            // Steps 2-5
+            //
+            do
+            {
+                
+                //
+                // Steps 2-3
+                //
+                rebuildcexe(state);
+                
+                //
+                // Step 4
+                //
+                // Calculate PX, PG
+                //
+                for(i_=0; i_<=nmain+nslack-1;i_++)
+                {
+                    px[i_] = x[i_];
+                }
+                for(i_=0; i_<=nmain+nslack-1;i_++)
+                {
+                    px[i_] = px[i_] - state.xe[i_];
+                }
+                for(i_=0; i_<=nmain+nslack-1;i_++)
+                {
+                    pg[i_] = g[i_];
+                }
+                for(i=0; i<=nmain+nslack-1; i++)
+                {
+                    if( state.activeconstraints[i] )
+                    {
+                        px[i] = 0;
+                        pg[i] = 0;
+                    }
+                }
+                for(i=0; i<=state.cecnt-1; i++)
+                {
+                    v = 0.0;
+                    for(i_=0; i_<=nmain+nslack-1;i_++)
+                    {
+                        v += px[i_]*state.cecurrent[i,i_];
+                    }
+                    for(i_=0; i_<=nmain+nslack-1;i_++)
+                    {
+                        px[i_] = px[i_] - v*state.cecurrent[i,i_];
+                    }
+                    v = 0.0;
+                    for(i_=0; i_<=nmain+nslack-1;i_++)
+                    {
+                        v += pg[i_]*state.cecurrent[i,i_];
+                    }
+                    for(i_=0; i_<=nmain+nslack-1;i_++)
+                    {
+                        pg[i_] = pg[i_] - v*state.cecurrent[i,i_];
+                    }
+                }
+                for(i_=0; i_<=nmain+nslack-1;i_++)
+                {
+                    px[i_] = px[i_] + state.xe[i_];
+                }
+                
+                //
+                // Step 5 (loop condition below)
+                //
+            }
+            while( additionalcheckforconstraints(state, px) );
+            
+            //
+            // Step 6
+            //
+            ferr = 0;
+            for(i=0; i<=state.cecnt-1; i++)
+            {
+                v = 0.0;
+                for(i_=0; i_<=nmain+nslack-1;i_++)
+                {
+                    v += px[i_]*state.ceeffective[i,i_];
+                }
+                v = v-state.ceeffective[i,nmain+nslack];
+                ferr = Math.Max(ferr, Math.Abs(v));
+            }
+            result = (double)(ferr)<=(double)(state.outerepsi);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Internal initialization subroutine
+        *************************************************************************/
+        private static void minbleicinitinternal(int n,
+            double[] x,
+            double diffstep,
+            minbleicstate state)
+        {
+            int i = 0;
+            double[,] c = new double[0,0];
+            int[] ct = new int[0];
+
+            state.nmain = n;
+            state.optdim = 0;
+            state.diffstep = diffstep;
+            state.bndloriginal = new double[n];
+            state.bndleffective = new double[n];
+            state.hasbndl = new bool[n];
+            state.bnduoriginal = new double[n];
+            state.bndueffective = new double[n];
+            state.hasbndu = new bool[n];
+            state.xstart = new double[n];
+            state.soriginal = new double[n];
+            state.x = new double[n];
+            state.g = new double[n];
+            for(i=0; i<=n-1; i++)
+            {
+                state.bndloriginal[i] = Double.NegativeInfinity;
+                state.hasbndl[i] = false;
+                state.bnduoriginal[i] = Double.PositiveInfinity;
+                state.hasbndu[i] = false;
+                state.soriginal[i] = 1.0;
+            }
+            minbleicsetlc(state, c, ct, 0);
+            minbleicsetinnercond(state, 0.0, 0.0, 0.0);
+            minbleicsetoutercond(state, 1.0E-6, 1.0E-6);
+            minbleicsetmaxits(state, 0);
+            minbleicsetxrep(state, false);
+            minbleicsetstpmax(state, 0.0);
+            minbleicsetprecdefault(state);
+            minbleicrestartfrom(state, x);
+        }
+
+
+    }
+    public class minlbfgs
+    {
+        public class minlbfgsstate
+        {
+            public int n;
+            public int m;
+            public double epsg;
+            public double epsf;
+            public double epsx;
+            public int maxits;
+            public bool xrep;
+            public double stpmax;
+            public double[] s;
+            public double diffstep;
+            public int nfev;
+            public int mcstage;
+            public int k;
+            public int q;
+            public int p;
+            public double[] rho;
+            public double[,] yk;
+            public double[,] sk;
+            public double[] theta;
+            public double[] d;
+            public double stp;
+            public double[] work;
+            public double fold;
+            public double trimthreshold;
+            public int prectype;
+            public double gammak;
+            public double[,] denseh;
+            public double[] diagh;
+            public double fbase;
+            public double fm2;
+            public double fm1;
+            public double fp1;
+            public double fp2;
+            public double[] autobuf;
+            public double[] x;
+            public double f;
+            public double[] g;
+            public bool needf;
+            public bool needfg;
+            public bool xupdated;
+            public rcommstate rstate;
+            public int repiterationscount;
+            public int repnfev;
+            public int repterminationtype;
+            public linmin.linminstate lstate;
+            public minlbfgsstate()
+            {
+                s = new double[0];
+                rho = new double[0];
+                yk = new double[0,0];
+                sk = new double[0,0];
+                theta = new double[0];
+                d = new double[0];
+                work = new double[0];
+                denseh = new double[0,0];
+                diagh = new double[0];
+                autobuf = new double[0];
+                x = new double[0];
+                g = new double[0];
+                rstate = new rcommstate();
+                lstate = new linmin.linminstate();
+            }
+        };
+
+
+        public class minlbfgsreport
+        {
+            public int iterationscount;
+            public int nfev;
+            public int terminationtype;
+        };
+
+
+
+
+        public const double gtol = 0.4;
+
+
+        /*************************************************************************
+                LIMITED MEMORY BFGS METHOD FOR LARGE SCALE OPTIMIZATION
+
+        DESCRIPTION:
+        The subroutine minimizes function F(x) of N arguments by  using  a  quasi-
+        Newton method (LBFGS scheme) which is optimized to use  a  minimum  amount
+        of memory.
+        The subroutine generates the approximation of an inverse Hessian matrix by
+        using information about the last M steps of the algorithm  (instead of N).
+        It lessens a required amount of memory from a value  of  order  N^2  to  a
+        value of order 2*N*M.
+
+
+        REQUIREMENTS:
+        Algorithm will request following information during its operation:
+        * function value F and its gradient G (simultaneously) at given point X
+
+
+        USAGE:
+        1. User initializes algorithm state with MinLBFGSCreate() call
+        2. User tunes solver parameters with MinLBFGSSetCond() MinLBFGSSetStpMax()
+           and other functions
+        3. User calls MinLBFGSOptimize() function which takes algorithm  state and
+           pointer (delegate, etc.) to callback function which calculates F/G.
+        4. User calls MinLBFGSResults() to get solution
+        5. Optionally user may call MinLBFGSRestartFrom() to solve another problem
+           with same N/M but another starting point and/or another function.
+           MinLBFGSRestartFrom() allows to reuse already initialized structure.
+
+
+        INPUT PARAMETERS:
+            N       -   problem dimension. N>0
+            M       -   number of corrections in the BFGS scheme of Hessian
+                        approximation update. Recommended value:  3<=M<=7. The smaller
+                        value causes worse convergence, the bigger will  not  cause  a
+                        considerably better convergence, but will cause a fall in  the
+                        performance. M<=N.
+            X       -   initial solution approximation, array[0..N-1].
+
+
+        OUTPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+            
+
+        NOTES:
+        1. you may tune stopping conditions with MinLBFGSSetCond() function
+        2. if target function contains exp() or other fast growing functions,  and
+           optimization algorithm makes too large steps which leads  to  overflow,
+           use MinLBFGSSetStpMax() function to bound algorithm's  steps.  However,
+           L-BFGS rarely needs such a tuning.
+
+
+          -- ALGLIB --
+             Copyright 02.04.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minlbfgscreate(int n,
+            int m,
+            double[] x,
+            minlbfgsstate state)
+        {
+            alglib.ap.assert(n>=1, "MinLBFGSCreate: N<1!");
+            alglib.ap.assert(m>=1, "MinLBFGSCreate: M<1");
+            alglib.ap.assert(m<=n, "MinLBFGSCreate: M>N");
+            alglib.ap.assert(alglib.ap.len(x)>=n, "MinLBFGSCreate: Length(X)<N!");
+            alglib.ap.assert(apserv.isfinitevector(x, n), "MinLBFGSCreate: X contains infinite or NaN values!");
+            minlbfgscreatex(n, m, x, 0, 0.0, state);
+        }
+
+
+        /*************************************************************************
+        The subroutine is finite difference variant of MinLBFGSCreate().  It  uses
+        finite differences in order to differentiate target function.
+
+        Description below contains information which is specific to  this function
+        only. We recommend to read comments on MinLBFGSCreate() in  order  to  get
+        more information about creation of LBFGS optimizer.
+
+        INPUT PARAMETERS:
+            N       -   problem dimension, N>0:
+                        * if given, only leading N elements of X are used
+                        * if not given, automatically determined from size of X
+            M       -   number of corrections in the BFGS scheme of Hessian
+                        approximation update. Recommended value:  3<=M<=7. The smaller
+                        value causes worse convergence, the bigger will  not  cause  a
+                        considerably better convergence, but will cause a fall in  the
+                        performance. M<=N.
+            X       -   starting point, array[0..N-1].
+            DiffStep-   differentiation step, >0
+
+        OUTPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+
+        NOTES:
+        1. algorithm uses 4-point central formula for differentiation.
+        2. differentiation step along I-th axis is equal to DiffStep*S[I] where
+           S[] is scaling vector which can be set by MinLBFGSSetScale() call.
+        3. we recommend you to use moderate values of  differentiation  step.  Too
+           large step will result in too large truncation  errors, while too small
+           step will result in too large numerical  errors.  1.0E-6  can  be  good
+           value to start with.
+        4. Numerical  differentiation  is   very   inefficient  -   one   gradient
+           calculation needs 4*N function evaluations. This function will work for
+           any N - either small (1...10), moderate (10...100) or  large  (100...).
+           However, performance penalty will be too severe for any N's except  for
+           small ones.
+           We should also say that code which relies on numerical  differentiation
+           is   less  robust  and  precise.  LBFGS  needs  exact  gradient values.
+           Imprecise gradient may slow  down  convergence,  especially  on  highly
+           nonlinear problems.
+           Thus  we  recommend to use this function for fast prototyping on small-
+           dimensional problems only, and to implement analytical gradient as soon
+           as possible.
+
+          -- ALGLIB --
+             Copyright 16.05.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minlbfgscreatef(int n,
+            int m,
+            double[] x,
+            double diffstep,
+            minlbfgsstate state)
+        {
+            alglib.ap.assert(n>=1, "MinLBFGSCreateF: N too small!");
+            alglib.ap.assert(m>=1, "MinLBFGSCreateF: M<1");
+            alglib.ap.assert(m<=n, "MinLBFGSCreateF: M>N");
+            alglib.ap.assert(alglib.ap.len(x)>=n, "MinLBFGSCreateF: Length(X)<N!");
+            alglib.ap.assert(apserv.isfinitevector(x, n), "MinLBFGSCreateF: X contains infinite or NaN values!");
+            alglib.ap.assert(math.isfinite(diffstep), "MinLBFGSCreateF: DiffStep is infinite or NaN!");
+            alglib.ap.assert((double)(diffstep)>(double)(0), "MinLBFGSCreateF: DiffStep is non-positive!");
+            minlbfgscreatex(n, m, x, 0, diffstep, state);
+        }
+
+
+        /*************************************************************************
+        This function sets stopping conditions for L-BFGS optimization algorithm.
+
+        INPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+            EpsG    -   >=0
+                        The  subroutine  finishes  its  work   if   the  condition
+                        |v|<EpsG is satisfied, where:
+                        * |.| means Euclidian norm
+                        * v - scaled gradient vector, v[i]=g[i]*s[i]
+                        * g - gradient
+                        * s - scaling coefficients set by MinLBFGSSetScale()
+            EpsF    -   >=0
+                        The  subroutine  finishes  its work if on k+1-th iteration
+                        the  condition  |F(k+1)-F(k)|<=EpsF*max{|F(k)|,|F(k+1)|,1}
+                        is satisfied.
+            EpsX    -   >=0
+                        The subroutine finishes its work if  on  k+1-th  iteration
+                        the condition |v|<=EpsX is fulfilled, where:
+                        * |.| means Euclidian norm
+                        * v - scaled step vector, v[i]=dx[i]/s[i]
+                        * dx - ste pvector, dx=X(k+1)-X(k)
+                        * s - scaling coefficients set by MinLBFGSSetScale()
+            MaxIts  -   maximum number of iterations. If MaxIts=0, the  number  of
+                        iterations is unlimited.
+
+        Passing EpsG=0, EpsF=0, EpsX=0 and MaxIts=0 (simultaneously) will lead to
+        automatic stopping criterion selection (small EpsX).
+
+          -- ALGLIB --
+             Copyright 02.04.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minlbfgssetcond(minlbfgsstate state,
+            double epsg,
+            double epsf,
+            double epsx,
+            int maxits)
+        {
+            alglib.ap.assert(math.isfinite(epsg), "MinLBFGSSetCond: EpsG is not finite number!");
+            alglib.ap.assert((double)(epsg)>=(double)(0), "MinLBFGSSetCond: negative EpsG!");
+            alglib.ap.assert(math.isfinite(epsf), "MinLBFGSSetCond: EpsF is not finite number!");
+            alglib.ap.assert((double)(epsf)>=(double)(0), "MinLBFGSSetCond: negative EpsF!");
+            alglib.ap.assert(math.isfinite(epsx), "MinLBFGSSetCond: EpsX is not finite number!");
+            alglib.ap.assert((double)(epsx)>=(double)(0), "MinLBFGSSetCond: negative EpsX!");
+            alglib.ap.assert(maxits>=0, "MinLBFGSSetCond: negative MaxIts!");
+            if( (((double)(epsg)==(double)(0) && (double)(epsf)==(double)(0)) && (double)(epsx)==(double)(0)) && maxits==0 )
+            {
+                epsx = 1.0E-6;
+            }
+            state.epsg = epsg;
+            state.epsf = epsf;
+            state.epsx = epsx;
+            state.maxits = maxits;
+        }
+
+
+        /*************************************************************************
+        This function turns on/off reporting.
+
+        INPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+            NeedXRep-   whether iteration reports are needed or not
+
+        If NeedXRep is True, algorithm will call rep() callback function if  it is
+        provided to MinLBFGSOptimize().
+
+
+          -- ALGLIB --
+             Copyright 02.04.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minlbfgssetxrep(minlbfgsstate state,
+            bool needxrep)
+        {
+            state.xrep = needxrep;
+        }
+
+
+        /*************************************************************************
+        This function sets maximum step length
+
+        INPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+            StpMax  -   maximum step length, >=0. Set StpMax to 0.0 (default),  if
+                        you don't want to limit step length.
+
+        Use this subroutine when you optimize target function which contains exp()
+        or  other  fast  growing  functions,  and optimization algorithm makes too
+        large  steps  which  leads  to overflow. This function allows us to reject
+        steps  that  are  too  large  (and  therefore  expose  us  to the possible
+        overflow) without actually calculating function value at the x+stp*d.
+
+          -- ALGLIB --
+             Copyright 02.04.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minlbfgssetstpmax(minlbfgsstate state,
+            double stpmax)
+        {
+            alglib.ap.assert(math.isfinite(stpmax), "MinLBFGSSetStpMax: StpMax is not finite!");
+            alglib.ap.assert((double)(stpmax)>=(double)(0), "MinLBFGSSetStpMax: StpMax<0!");
+            state.stpmax = stpmax;
+        }
+
+
+        /*************************************************************************
+        This function sets scaling coefficients for LBFGS optimizer.
+
+        ALGLIB optimizers use scaling matrices to test stopping  conditions  (step
+        size and gradient are scaled before comparison with tolerances).  Scale of
+        the I-th variable is a translation invariant measure of:
+        a) "how large" the variable is
+        b) how large the step should be to make significant changes in the function
+
+        Scaling is also used by finite difference variant of the optimizer  - step
+        along I-th axis is equal to DiffStep*S[I].
+
+        In  most  optimizers  (and  in  the  LBFGS  too)  scaling is NOT a form of
+        preconditioning. It just  affects  stopping  conditions.  You  should  set
+        preconditioner  by  separate  call  to  one  of  the  MinLBFGSSetPrec...()
+        functions.
+
+        There  is  special  preconditioning  mode, however,  which  uses   scaling
+        coefficients to form diagonal preconditioning matrix. You  can  turn  this
+        mode on, if you want.   But  you should understand that scaling is not the
+        same thing as preconditioning - these are two different, although  related
+        forms of tuning solver.
+
+        INPUT PARAMETERS:
+            State   -   structure stores algorithm state
+            S       -   array[N], non-zero scaling coefficients
+                        S[i] may be negative, sign doesn't matter.
+
+          -- ALGLIB --
+             Copyright 14.01.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minlbfgssetscale(minlbfgsstate state,
+            double[] s)
+        {
+            int i = 0;
+
+            alglib.ap.assert(alglib.ap.len(s)>=state.n, "MinLBFGSSetScale: Length(S)<N");
+            for(i=0; i<=state.n-1; i++)
+            {
+                alglib.ap.assert(math.isfinite(s[i]), "MinLBFGSSetScale: S contains infinite or NAN elements");
+                alglib.ap.assert((double)(s[i])!=(double)(0), "MinLBFGSSetScale: S contains zero elements");
+                state.s[i] = Math.Abs(s[i]);
+            }
+        }
+
+
+        /*************************************************************************
+        Extended subroutine for internal use only.
+
+        Accepts additional parameters:
+
+            Flags - additional settings:
+                    * Flags = 0     means no additional settings
+                    * Flags = 1     "do not allocate memory". used when solving
+                                    a many subsequent tasks with  same N/M  values.
+                                    First  call MUST  be without this flag bit set,
+                                    subsequent  calls   of   MinLBFGS   with   same
+                                    MinLBFGSState structure can set Flags to 1.
+            DiffStep - numerical differentiation step
+
+          -- ALGLIB --
+             Copyright 02.04.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minlbfgscreatex(int n,
+            int m,
+            double[] x,
+            int flags,
+            double diffstep,
+            minlbfgsstate state)
+        {
+            bool allocatemem = new bool();
+            int i = 0;
+
+            alglib.ap.assert(n>=1, "MinLBFGS: N too small!");
+            alglib.ap.assert(m>=1, "MinLBFGS: M too small!");
+            alglib.ap.assert(m<=n, "MinLBFGS: M too large!");
+            
+            //
+            // Initialize
+            //
+            state.diffstep = diffstep;
+            state.n = n;
+            state.m = m;
+            allocatemem = flags%2==0;
+            flags = flags/2;
+            if( allocatemem )
+            {
+                state.rho = new double[m];
+                state.theta = new double[m];
+                state.yk = new double[m, n];
+                state.sk = new double[m, n];
+                state.d = new double[n];
+                state.x = new double[n];
+                state.s = new double[n];
+                state.g = new double[n];
+                state.work = new double[n];
+            }
+            minlbfgssetcond(state, 0, 0, 0, 0);
+            minlbfgssetxrep(state, false);
+            minlbfgssetstpmax(state, 0);
+            minlbfgsrestartfrom(state, x);
+            for(i=0; i<=n-1; i++)
+            {
+                state.s[i] = 1.0;
+            }
+            state.prectype = 0;
+        }
+
+
+        /*************************************************************************
+        Modification  of  the  preconditioner:  default  preconditioner    (simple
+        scaling, same for all elements of X) is used.
+
+        INPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+
+        NOTE:  you  can  change  preconditioner  "on  the  fly",  during algorithm
+        iterations.
+
+          -- ALGLIB --
+             Copyright 13.10.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minlbfgssetprecdefault(minlbfgsstate state)
+        {
+            state.prectype = 0;
+        }
+
+
+        /*************************************************************************
+        Modification of the preconditioner: Cholesky factorization of  approximate
+        Hessian is used.
+
+        INPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+            P       -   triangular preconditioner, Cholesky factorization of
+                        the approximate Hessian. array[0..N-1,0..N-1],
+                        (if larger, only leading N elements are used).
+            IsUpper -   whether upper or lower triangle of P is given
+                        (other triangle is not referenced)
+
+        After call to this function preconditioner is changed to P  (P  is  copied
+        into the internal buffer).
+
+        NOTE:  you  can  change  preconditioner  "on  the  fly",  during algorithm
+        iterations.
+
+        NOTE 2:  P  should  be nonsingular. Exception will be thrown otherwise.
+
+          -- ALGLIB --
+             Copyright 13.10.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minlbfgssetpreccholesky(minlbfgsstate state,
+            double[,] p,
+            bool isupper)
+        {
+            int i = 0;
+            double mx = 0;
+
+            alglib.ap.assert(apserv.isfinitertrmatrix(p, state.n, isupper), "MinLBFGSSetPrecCholesky: P contains infinite or NAN values!");
+            mx = 0;
+            for(i=0; i<=state.n-1; i++)
+            {
+                mx = Math.Max(mx, Math.Abs(p[i,i]));
+            }
+            alglib.ap.assert((double)(mx)>(double)(0), "MinLBFGSSetPrecCholesky: P is strictly singular!");
+            if( alglib.ap.rows(state.denseh)<state.n || alglib.ap.cols(state.denseh)<state.n )
+            {
+                state.denseh = new double[state.n, state.n];
+            }
+            state.prectype = 1;
+            if( isupper )
+            {
+                ablas.rmatrixcopy(state.n, state.n, p, 0, 0, ref state.denseh, 0, 0);
+            }
+            else
+            {
+                ablas.rmatrixtranspose(state.n, state.n, p, 0, 0, ref state.denseh, 0, 0);
+            }
+        }
+
+
+        /*************************************************************************
+        Modification  of  the  preconditioner:  diagonal of approximate Hessian is
+        used.
+
+        INPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+            D       -   diagonal of the approximate Hessian, array[0..N-1],
+                        (if larger, only leading N elements are used).
+
+        NOTE:  you  can  change  preconditioner  "on  the  fly",  during algorithm
+        iterations.
+
+        NOTE 2: D[i] should be positive. Exception will be thrown otherwise.
+
+        NOTE 3: you should pass diagonal of approximate Hessian - NOT ITS INVERSE.
+
+          -- ALGLIB --
+             Copyright 13.10.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minlbfgssetprecdiag(minlbfgsstate state,
+            double[] d)
+        {
+            int i = 0;
+
+            alglib.ap.assert(alglib.ap.len(d)>=state.n, "MinLBFGSSetPrecDiag: D is too short");
+            for(i=0; i<=state.n-1; i++)
+            {
+                alglib.ap.assert(math.isfinite(d[i]), "MinLBFGSSetPrecDiag: D contains infinite or NAN elements");
+                alglib.ap.assert((double)(d[i])>(double)(0), "MinLBFGSSetPrecDiag: D contains non-positive elements");
+            }
+            apserv.rvectorsetlengthatleast(ref state.diagh, state.n);
+            state.prectype = 2;
+            for(i=0; i<=state.n-1; i++)
+            {
+                state.diagh[i] = d[i];
+            }
+        }
+
+
+        /*************************************************************************
+        Modification of the preconditioner: scale-based diagonal preconditioning.
+
+        This preconditioning mode can be useful when you  don't  have  approximate
+        diagonal of Hessian, but you know that your  variables  are  badly  scaled
+        (for  example,  one  variable is in [1,10], and another in [1000,100000]),
+        and most part of the ill-conditioning comes from different scales of vars.
+
+        In this case simple  scale-based  preconditioner,  with H[i] = 1/(s[i]^2),
+        can greatly improve convergence.
+
+        IMPRTANT: you should set scale of your variables  with  MinLBFGSSetScale()
+        call  (before  or after MinLBFGSSetPrecScale() call). Without knowledge of
+        the scale of your variables scale-based preconditioner will be  just  unit
+        matrix.
+
+        INPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+
+          -- ALGLIB --
+             Copyright 13.10.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minlbfgssetprecscale(minlbfgsstate state)
+        {
+            state.prectype = 3;
+        }
+
+
+        /*************************************************************************
+        NOTES:
+
+        1. This function has two different implementations: one which  uses  exact
+           (analytical) user-supplied gradient,  and one which uses function value
+           only  and  numerically  differentiates  function  in  order  to  obtain
+           gradient.
+
+           Depending  on  the  specific  function  used to create optimizer object
+           (either MinLBFGSCreate() for analytical gradient  or  MinLBFGSCreateF()
+           for numerical differentiation) you should choose appropriate variant of
+           MinLBFGSOptimize() - one  which  accepts  function  AND gradient or one
+           which accepts function ONLY.
+
+           Be careful to choose variant of MinLBFGSOptimize() which corresponds to
+           your optimization scheme! Table below lists different  combinations  of
+           callback (function/gradient) passed to MinLBFGSOptimize()  and specific
+           function used to create optimizer.
+
+
+                             |         USER PASSED TO MinLBFGSOptimize()
+           CREATED WITH      |  function only   |  function and gradient
+           ------------------------------------------------------------
+           MinLBFGSCreateF() |     work                FAIL
+           MinLBFGSCreate()  |     FAIL                work
+
+           Here "FAIL" denotes inappropriate combinations  of  optimizer  creation
+           function  and  MinLBFGSOptimize()  version.   Attemps   to   use   such
+           combination (for example, to create optimizer with MinLBFGSCreateF() and
+           to pass gradient information to MinCGOptimize()) will lead to exception
+           being thrown. Either  you  did  not pass gradient when it WAS needed or
+           you passed gradient when it was NOT needed.
+
+          -- ALGLIB --
+             Copyright 20.03.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static bool minlbfgsiteration(minlbfgsstate state)
+        {
+            bool result = new bool();
+            int n = 0;
+            int m = 0;
+            int i = 0;
+            int j = 0;
+            int ic = 0;
+            int mcinfo = 0;
+            double v = 0;
+            double vv = 0;
+            int i_ = 0;
+
+            
+            //
+            // Reverse communication preparations
+            // I know it looks ugly, but it works the same way
+            // anywhere from C++ to Python.
+            //
+            // This code initializes locals by:
+            // * random values determined during code
+            //   generation - on first subroutine call
+            // * values from previous call - on subsequent calls
+            //
+            if( state.rstate.stage>=0 )
+            {
+                n = state.rstate.ia[0];
+                m = state.rstate.ia[1];
+                i = state.rstate.ia[2];
+                j = state.rstate.ia[3];
+                ic = state.rstate.ia[4];
+                mcinfo = state.rstate.ia[5];
+                v = state.rstate.ra[0];
+                vv = state.rstate.ra[1];
+            }
+            else
+            {
+                n = -983;
+                m = -989;
+                i = -834;
+                j = 900;
+                ic = -287;
+                mcinfo = 364;
+                v = 214;
+                vv = -338;
+            }
+            if( state.rstate.stage==0 )
+            {
+                goto lbl_0;
+            }
+            if( state.rstate.stage==1 )
+            {
+                goto lbl_1;
+            }
+            if( state.rstate.stage==2 )
+            {
+                goto lbl_2;
+            }
+            if( state.rstate.stage==3 )
+            {
+                goto lbl_3;
+            }
+            if( state.rstate.stage==4 )
+            {
+                goto lbl_4;
+            }
+            if( state.rstate.stage==5 )
+            {
+                goto lbl_5;
+            }
+            if( state.rstate.stage==6 )
+            {
+                goto lbl_6;
+            }
+            if( state.rstate.stage==7 )
+            {
+                goto lbl_7;
+            }
+            if( state.rstate.stage==8 )
+            {
+                goto lbl_8;
+            }
+            if( state.rstate.stage==9 )
+            {
+                goto lbl_9;
+            }
+            if( state.rstate.stage==10 )
+            {
+                goto lbl_10;
+            }
+            if( state.rstate.stage==11 )
+            {
+                goto lbl_11;
+            }
+            if( state.rstate.stage==12 )
+            {
+                goto lbl_12;
+            }
+            if( state.rstate.stage==13 )
+            {
+                goto lbl_13;
+            }
+            
+            //
+            // Routine body
+            //
+            
+            //
+            // Unload frequently used variables from State structure
+            // (just for typing convinience)
+            //
+            n = state.n;
+            m = state.m;
+            state.repterminationtype = 0;
+            state.repiterationscount = 0;
+            state.repnfev = 0;
+            
+            //
+            // Calculate F/G at the initial point
+            //
+            clearrequestfields(state);
+            if( (double)(state.diffstep)!=(double)(0) )
+            {
+                goto lbl_14;
+            }
+            state.needfg = true;
+            state.rstate.stage = 0;
+            goto lbl_rcomm;
+        lbl_0:
+            state.needfg = false;
+            goto lbl_15;
+        lbl_14:
+            state.needf = true;
+            state.rstate.stage = 1;
+            goto lbl_rcomm;
+        lbl_1:
+            state.fbase = state.f;
+            i = 0;
+        lbl_16:
+            if( i>n-1 )
+            {
+                goto lbl_18;
+            }
+            v = state.x[i];
+            state.x[i] = v-state.diffstep*state.s[i];
+            state.rstate.stage = 2;
+            goto lbl_rcomm;
+        lbl_2:
+            state.fm2 = state.f;
+            state.x[i] = v-0.5*state.diffstep*state.s[i];
+            state.rstate.stage = 3;
+            goto lbl_rcomm;
+        lbl_3:
+            state.fm1 = state.f;
+            state.x[i] = v+0.5*state.diffstep*state.s[i];
+            state.rstate.stage = 4;
+            goto lbl_rcomm;
+        lbl_4:
+            state.fp1 = state.f;
+            state.x[i] = v+state.diffstep*state.s[i];
+            state.rstate.stage = 5;
+            goto lbl_rcomm;
+        lbl_5:
+            state.fp2 = state.f;
+            state.x[i] = v;
+            state.g[i] = (8*(state.fp1-state.fm1)-(state.fp2-state.fm2))/(6*state.diffstep*state.s[i]);
+            i = i+1;
+            goto lbl_16;
+        lbl_18:
+            state.f = state.fbase;
+            state.needf = false;
+        lbl_15:
+            optserv.trimprepare(state.f, ref state.trimthreshold);
+            if( !state.xrep )
+            {
+                goto lbl_19;
+            }
+            clearrequestfields(state);
+            state.xupdated = true;
+            state.rstate.stage = 6;
+            goto lbl_rcomm;
+        lbl_6:
+            state.xupdated = false;
+        lbl_19:
+            state.repnfev = 1;
+            state.fold = state.f;
+            v = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                v = v+math.sqr(state.g[i]*state.s[i]);
+            }
+            if( (double)(Math.Sqrt(v))<=(double)(state.epsg) )
+            {
+                state.repterminationtype = 4;
+                result = false;
+                return result;
+            }
+            
+            //
+            // Choose initial step and direction.
+            // Apply preconditioner, if we have something other than default.
+            //
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.d[i_] = -state.g[i_];
+            }
+            if( state.prectype==0 )
+            {
+                
+                //
+                // Default preconditioner is used, but we can't use it before iterations will start
+                //
+                v = 0.0;
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    v += state.g[i_]*state.g[i_];
+                }
+                v = Math.Sqrt(v);
+                if( (double)(state.stpmax)==(double)(0) )
+                {
+                    state.stp = Math.Min(1.0/v, 1);
+                }
+                else
+                {
+                    state.stp = Math.Min(1.0/v, state.stpmax);
+                }
+            }
+            if( state.prectype==1 )
+            {
+                
+                //
+                // Cholesky preconditioner is used
+                //
+                fbls.fblscholeskysolve(state.denseh, 1.0, n, true, ref state.d, ref state.autobuf);
+                state.stp = 1;
+            }
+            if( state.prectype==2 )
+            {
+                
+                //
+                // diagonal approximation is used
+                //
+                for(i=0; i<=n-1; i++)
+                {
+                    state.d[i] = state.d[i]/state.diagh[i];
+                }
+                state.stp = 1;
+            }
+            if( state.prectype==3 )
+            {
+                
+                //
+                // scale-based preconditioner is used
+                //
+                for(i=0; i<=n-1; i++)
+                {
+                    state.d[i] = state.d[i]*state.s[i]*state.s[i];
+                }
+                state.stp = 1;
+            }
+            
+            //
+            // Main cycle
+            //
+            state.k = 0;
+        lbl_21:
+            if( false )
+            {
+                goto lbl_22;
+            }
+            
+            //
+            // Main cycle: prepare to 1-D line search
+            //
+            state.p = state.k%m;
+            state.q = Math.Min(state.k, m-1);
+            
+            //
+            // Store X[k], G[k]
+            //
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.sk[state.p,i_] = -state.x[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.yk[state.p,i_] = -state.g[i_];
+            }
+            
+            //
+            // Minimize F(x+alpha*d)
+            // Calculate S[k], Y[k]
+            //
+            state.mcstage = 0;
+            if( state.k!=0 )
+            {
+                state.stp = 1.0;
+            }
+            linmin.linminnormalized(ref state.d, ref state.stp, n);
+            linmin.mcsrch(n, ref state.x, ref state.f, ref state.g, state.d, ref state.stp, state.stpmax, gtol, ref mcinfo, ref state.nfev, ref state.work, state.lstate, ref state.mcstage);
+        lbl_23:
+            if( state.mcstage==0 )
+            {
+                goto lbl_24;
+            }
+            clearrequestfields(state);
+            if( (double)(state.diffstep)!=(double)(0) )
+            {
+                goto lbl_25;
+            }
+            state.needfg = true;
+            state.rstate.stage = 7;
+            goto lbl_rcomm;
+        lbl_7:
+            state.needfg = false;
+            goto lbl_26;
+        lbl_25:
+            state.needf = true;
+            state.rstate.stage = 8;
+            goto lbl_rcomm;
+        lbl_8:
+            state.fbase = state.f;
+            i = 0;
+        lbl_27:
+            if( i>n-1 )
+            {
+                goto lbl_29;
+            }
+            v = state.x[i];
+            state.x[i] = v-state.diffstep*state.s[i];
+            state.rstate.stage = 9;
+            goto lbl_rcomm;
+        lbl_9:
+            state.fm2 = state.f;
+            state.x[i] = v-0.5*state.diffstep*state.s[i];
+            state.rstate.stage = 10;
+            goto lbl_rcomm;
+        lbl_10:
+            state.fm1 = state.f;
+            state.x[i] = v+0.5*state.diffstep*state.s[i];
+            state.rstate.stage = 11;
+            goto lbl_rcomm;
+        lbl_11:
+            state.fp1 = state.f;
+            state.x[i] = v+state.diffstep*state.s[i];
+            state.rstate.stage = 12;
+            goto lbl_rcomm;
+        lbl_12:
+            state.fp2 = state.f;
+            state.x[i] = v;
+            state.g[i] = (8*(state.fp1-state.fm1)-(state.fp2-state.fm2))/(6*state.diffstep*state.s[i]);
+            i = i+1;
+            goto lbl_27;
+        lbl_29:
+            state.f = state.fbase;
+            state.needf = false;
+        lbl_26:
+            optserv.trimfunction(ref state.f, ref state.g, n, state.trimthreshold);
+            linmin.mcsrch(n, ref state.x, ref state.f, ref state.g, state.d, ref state.stp, state.stpmax, gtol, ref mcinfo, ref state.nfev, ref state.work, state.lstate, ref state.mcstage);
+            goto lbl_23;
+        lbl_24:
+            if( !state.xrep )
+            {
+                goto lbl_30;
+            }
+            
+            //
+            // report
+            //
+            clearrequestfields(state);
+            state.xupdated = true;
+            state.rstate.stage = 13;
+            goto lbl_rcomm;
+        lbl_13:
+            state.xupdated = false;
+        lbl_30:
+            state.repnfev = state.repnfev+state.nfev;
+            state.repiterationscount = state.repiterationscount+1;
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.sk[state.p,i_] = state.sk[state.p,i_] + state.x[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.yk[state.p,i_] = state.yk[state.p,i_] + state.g[i_];
+            }
+            
+            //
+            // Stopping conditions
+            //
+            if( state.repiterationscount>=state.maxits && state.maxits>0 )
+            {
+                
+                //
+                // Too many iterations
+                //
+                state.repterminationtype = 5;
+                result = false;
+                return result;
+            }
+            v = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                v = v+math.sqr(state.g[i]*state.s[i]);
+            }
+            if( (double)(Math.Sqrt(v))<=(double)(state.epsg) )
+            {
+                
+                //
+                // Gradient is small enough
+                //
+                state.repterminationtype = 4;
+                result = false;
+                return result;
+            }
+            if( (double)(state.fold-state.f)<=(double)(state.epsf*Math.Max(Math.Abs(state.fold), Math.Max(Math.Abs(state.f), 1.0))) )
+            {
+                
+                //
+                // F(k+1)-F(k) is small enough
+                //
+                state.repterminationtype = 1;
+                result = false;
+                return result;
+            }
+            v = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                v = v+math.sqr(state.sk[state.p,i]/state.s[i]);
+            }
+            if( (double)(Math.Sqrt(v))<=(double)(state.epsx) )
+            {
+                
+                //
+                // X(k+1)-X(k) is small enough
+                //
+                state.repterminationtype = 2;
+                result = false;
+                return result;
+            }
+            
+            //
+            // If Wolfe conditions are satisfied, we can update
+            // limited memory model.
+            //
+            // However, if conditions are not satisfied (NFEV limit is met,
+            // function is too wild, ...), we'll skip L-BFGS update
+            //
+            if( mcinfo!=1 )
+            {
+                
+                //
+                // Skip update.
+                //
+                // In such cases we'll initialize search direction by
+                // antigradient vector, because it  leads to more
+                // transparent code with less number of special cases
+                //
+                state.fold = state.f;
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    state.d[i_] = -state.g[i_];
+                }
+            }
+            else
+            {
+                
+                //
+                // Calculate Rho[k], GammaK
+                //
+                v = 0.0;
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    v += state.yk[state.p,i_]*state.sk[state.p,i_];
+                }
+                vv = 0.0;
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    vv += state.yk[state.p,i_]*state.yk[state.p,i_];
+                }
+                if( (double)(v)==(double)(0) || (double)(vv)==(double)(0) )
+                {
+                    
+                    //
+                    // Rounding errors make further iterations impossible.
+                    //
+                    state.repterminationtype = -2;
+                    result = false;
+                    return result;
+                }
+                state.rho[state.p] = 1/v;
+                state.gammak = v/vv;
+                
+                //
+                //  Calculate d(k+1) = -H(k+1)*g(k+1)
+                //
+                //  for I:=K downto K-Q do
+                //      V = s(i)^T * work(iteration:I)
+                //      theta(i) = V
+                //      work(iteration:I+1) = work(iteration:I) - V*Rho(i)*y(i)
+                //  work(last iteration) = H0*work(last iteration) - preconditioner
+                //  for I:=K-Q to K do
+                //      V = y(i)^T*work(iteration:I)
+                //      work(iteration:I+1) = work(iteration:I) +(-V+theta(i))*Rho(i)*s(i)
+                //
+                //  NOW WORK CONTAINS d(k+1)
+                //
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    state.work[i_] = state.g[i_];
+                }
+                for(i=state.k; i>=state.k-state.q; i--)
+                {
+                    ic = i%m;
+                    v = 0.0;
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        v += state.sk[ic,i_]*state.work[i_];
+                    }
+                    state.theta[ic] = v;
+                    vv = v*state.rho[ic];
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        state.work[i_] = state.work[i_] - vv*state.yk[ic,i_];
+                    }
+                }
+                if( state.prectype==0 )
+                {
+                    
+                    //
+                    // Simple preconditioner is used
+                    //
+                    v = state.gammak;
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        state.work[i_] = v*state.work[i_];
+                    }
+                }
+                if( state.prectype==1 )
+                {
+                    
+                    //
+                    // Cholesky preconditioner is used
+                    //
+                    fbls.fblscholeskysolve(state.denseh, 1, n, true, ref state.work, ref state.autobuf);
+                }
+                if( state.prectype==2 )
+                {
+                    
+                    //
+                    // diagonal approximation is used
+                    //
+                    for(i=0; i<=n-1; i++)
+                    {
+                        state.work[i] = state.work[i]/state.diagh[i];
+                    }
+                }
+                if( state.prectype==3 )
+                {
+                    
+                    //
+                    // scale-based preconditioner is used
+                    //
+                    for(i=0; i<=n-1; i++)
+                    {
+                        state.work[i] = state.work[i]*state.s[i]*state.s[i];
+                    }
+                }
+                for(i=state.k-state.q; i<=state.k; i++)
+                {
+                    ic = i%m;
+                    v = 0.0;
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        v += state.yk[ic,i_]*state.work[i_];
+                    }
+                    vv = state.rho[ic]*(-v+state.theta[ic]);
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        state.work[i_] = state.work[i_] + vv*state.sk[ic,i_];
+                    }
+                }
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    state.d[i_] = -state.work[i_];
+                }
+                
+                //
+                // Next step
+                //
+                state.fold = state.f;
+                state.k = state.k+1;
+            }
+            goto lbl_21;
+        lbl_22:
+            result = false;
+            return result;
+            
+            //
+            // Saving state
+            //
+        lbl_rcomm:
+            result = true;
+            state.rstate.ia[0] = n;
+            state.rstate.ia[1] = m;
+            state.rstate.ia[2] = i;
+            state.rstate.ia[3] = j;
+            state.rstate.ia[4] = ic;
+            state.rstate.ia[5] = mcinfo;
+            state.rstate.ra[0] = v;
+            state.rstate.ra[1] = vv;
+            return result;
+        }
+
+
+        /*************************************************************************
+        L-BFGS algorithm results
+
+        INPUT PARAMETERS:
+            State   -   algorithm state
+
+        OUTPUT PARAMETERS:
+            X       -   array[0..N-1], solution
+            Rep     -   optimization report:
+                        * Rep.TerminationType completetion code:
+                            * -2    rounding errors prevent further improvement.
+                                    X contains best point found.
+                            * -1    incorrect parameters were specified
+                            *  1    relative function improvement is no more than
+                                    EpsF.
+                            *  2    relative step is no more than EpsX.
+                            *  4    gradient norm is no more than EpsG
+                            *  5    MaxIts steps was taken
+                            *  7    stopping conditions are too stringent,
+                                    further improvement is impossible
+                        * Rep.IterationsCount contains iterations count
+                        * NFEV countains number of function calculations
+
+          -- ALGLIB --
+             Copyright 02.04.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minlbfgsresults(minlbfgsstate state,
+            ref double[] x,
+            minlbfgsreport rep)
+        {
+            x = new double[0];
+
+            minlbfgsresultsbuf(state, ref x, rep);
+        }
+
+
+        /*************************************************************************
+        L-BFGS algorithm results
+
+        Buffered implementation of MinLBFGSResults which uses pre-allocated buffer
+        to store X[]. If buffer size is  too  small,  it  resizes  buffer.  It  is
+        intended to be used in the inner cycles of performance critical algorithms
+        where array reallocation penalty is too large to be ignored.
+
+          -- ALGLIB --
+             Copyright 20.08.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minlbfgsresultsbuf(minlbfgsstate state,
+            ref double[] x,
+            minlbfgsreport rep)
+        {
+            int i_ = 0;
+
+            if( alglib.ap.len(x)<state.n )
+            {
+                x = new double[state.n];
+            }
+            for(i_=0; i_<=state.n-1;i_++)
+            {
+                x[i_] = state.x[i_];
+            }
+            rep.iterationscount = state.repiterationscount;
+            rep.nfev = state.repnfev;
+            rep.terminationtype = state.repterminationtype;
+        }
+
+
+        /*************************************************************************
+        This  subroutine restarts LBFGS algorithm from new point. All optimization
+        parameters are left unchanged.
+
+        This  function  allows  to  solve multiple  optimization  problems  (which
+        must have same number of dimensions) without object reallocation penalty.
+
+        INPUT PARAMETERS:
+            State   -   structure used to store algorithm state
+            X       -   new starting point.
+
+          -- ALGLIB --
+             Copyright 30.07.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minlbfgsrestartfrom(minlbfgsstate state,
+            double[] x)
+        {
+            int i_ = 0;
+
+            alglib.ap.assert(alglib.ap.len(x)>=state.n, "MinLBFGSRestartFrom: Length(X)<N!");
+            alglib.ap.assert(apserv.isfinitevector(x, state.n), "MinLBFGSRestartFrom: X contains infinite or NaN values!");
+            for(i_=0; i_<=state.n-1;i_++)
+            {
+                state.x[i_] = x[i_];
+            }
+            state.rstate.ia = new int[5+1];
+            state.rstate.ra = new double[1+1];
+            state.rstate.stage = -1;
+            clearrequestfields(state);
+        }
+
+
+        /*************************************************************************
+        Clears request fileds (to be sure that we don't forgot to clear something)
+        *************************************************************************/
+        private static void clearrequestfields(minlbfgsstate state)
+        {
+            state.needf = false;
+            state.needfg = false;
+            state.xupdated = false;
+        }
+
+
+    }
+    public class minqp
+    {
+        /*************************************************************************
+        This object stores nonlinear optimizer state.
+        You should use functions provided by MinQP subpackage to work with this
+        object
+        *************************************************************************/
+        public class minqpstate
+        {
+            public int n;
+            public int algokind;
+            public int akind;
+            public double[,] densea;
+            public double[] b;
+            public double[] bndl;
+            public double[] bndu;
+            public bool[] havebndl;
+            public bool[] havebndu;
+            public double[] xorigin;
+            public double[] startx;
+            public bool havex;
+            public double[] xc;
+            public double[] gc;
+            public double[] dc;
+            public double[] xcand0;
+            public double[] xcand1;
+            public double[] xn;
+            public double[] pg;
+            public bool[] activeset;
+            public double constterm;
+            public double[] workbndl;
+            public double[] workbndu;
+            public int repinneriterationscount;
+            public int repouteriterationscount;
+            public int repncholesky;
+            public int repnmv;
+            public int repterminationtype;
+            public double[] tmp0;
+            public double[] tmp1;
+            public int[] itmp0;
+            public int[] p2;
+            public double[,] bufa;
+            public double[] bufb;
+            public double[] bufx;
+            public apserv.apbuffers buf;
+            public minqpstate()
+            {
+                densea = new double[0,0];
+                b = new double[0];
+                bndl = new double[0];
+                bndu = new double[0];
+                havebndl = new bool[0];
+                havebndu = new bool[0];
+                xorigin = new double[0];
+                startx = new double[0];
+                xc = new double[0];
+                gc = new double[0];
+                dc = new double[0];
+                xcand0 = new double[0];
+                xcand1 = new double[0];
+                xn = new double[0];
+                pg = new double[0];
+                activeset = new bool[0];
+                workbndl = new double[0];
+                workbndu = new double[0];
+                tmp0 = new double[0];
+                tmp1 = new double[0];
+                itmp0 = new int[0];
+                p2 = new int[0];
+                bufa = new double[0,0];
+                bufb = new double[0];
+                bufx = new double[0];
+                buf = new apserv.apbuffers();
+            }
+        };
+
+
+        /*************************************************************************
+        This structure stores optimization report:
+        * InnerIterationsCount      number of inner iterations
+        * OuterIterationsCount      number of outer iterations
+        * NCholesky                 number of Cholesky decomposition
+        * NMV                       number of matrix-vector products
+                                    (only products calculated as part of iterative
+                                    process are counted)
+        * TerminationType           completion code (see below)
+
+        Completion codes:
+        * -5    inappropriate solver was used:
+                * Cholesky solver for semidefinite or indefinite problems
+                * Cholesky solver for problems with non-boundary constraints
+        * -3    inconsistent constraints (or, maybe, feasible point is
+                too hard to find). If you are sure that constraints are feasible,
+                try to restart optimizer with better initial approximation.
+        * -1    solver error
+        *  4    successful completion
+        *  5    MaxIts steps was taken
+        *  7    stopping conditions are too stringent,
+                further improvement is impossible,
+                X contains best point found so far.
+        *************************************************************************/
+        public class minqpreport
+        {
+            public int inneriterationscount;
+            public int outeriterationscount;
+            public int nmv;
+            public int ncholesky;
+            public int terminationtype;
+        };
+
+
+
+
+        /*************************************************************************
+                            CONSTRAINED QUADRATIC PROGRAMMING
+
+        The subroutine creates QP optimizer. After initial creation,  it  contains
+        default optimization problem with zero quadratic and linear terms  and  no
+        constraints. You should set quadratic/linear terms with calls to functions
+        provided by MinQP subpackage.
+
+        INPUT PARAMETERS:
+            N       -   problem size
+            
+        OUTPUT PARAMETERS:
+            State   -   optimizer with zero quadratic/linear terms
+                        and no constraints
+
+          -- ALGLIB --
+             Copyright 11.01.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minqpcreate(int n,
+            minqpstate state)
+        {
+            int i = 0;
+
+            alglib.ap.assert(n>=1, "MinQPCreate: N<1");
+            
+            //
+            // initialize QP solver
+            //
+            state.n = n;
+            state.akind = -1;
+            state.repterminationtype = 0;
+            state.b = new double[n];
+            state.bndl = new double[n];
+            state.bndu = new double[n];
+            state.workbndl = new double[n];
+            state.workbndu = new double[n];
+            state.havebndl = new bool[n];
+            state.havebndu = new bool[n];
+            state.startx = new double[n];
+            state.xorigin = new double[n];
+            state.xcand0 = new double[n];
+            state.xcand1 = new double[n];
+            state.xc = new double[n];
+            state.xn = new double[n];
+            state.gc = new double[n];
+            state.pg = new double[n];
+            state.dc = new double[n];
+            state.activeset = new bool[n];
+            for(i=0; i<=n-1; i++)
+            {
+                state.b[i] = 0.0;
+                state.bndl[i] = Double.NegativeInfinity;
+                state.bndu[i] = Double.PositiveInfinity;
+                state.havebndl[i] = false;
+                state.havebndu[i] = false;
+                state.startx[i] = 0.0;
+                state.xorigin[i] = 0.0;
+            }
+            state.havex = false;
+            minqpsetalgocholesky(state);
+        }
+
+
+        /*************************************************************************
+        This function sets linear term for QP solver.
+
+        By default, linear term is zero.
+
+        INPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+            B       -   linear term, array[N].
+
+          -- ALGLIB --
+             Copyright 11.01.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minqpsetlinearterm(minqpstate state,
+            double[] b)
+        {
+            int n = 0;
+
+            n = state.n;
+            alglib.ap.assert(alglib.ap.len(b)>=n, "MinQPSetLinearTerm: Length(B)<N");
+            alglib.ap.assert(apserv.isfinitevector(b, n), "MinQPSetLinearTerm: B contains infinite or NaN elements");
+            minqpsetlineartermfast(state, b);
+        }
+
+
+        /*************************************************************************
+        This function sets quadratic term for QP solver.
+
+        By default quadratic term is zero.
+
+        IMPORTANT: this solver minimizes following  function:
+            f(x) = 0.5*x'*A*x + b'*x.
+        Note that quadratic term has 0.5 before it. So if  you  want  to  minimize
+            f(x) = x^2 + x
+        you should rewrite your problem as follows:
+            f(x) = 0.5*(2*x^2) + x
+        and your matrix A will be equal to [[2.0]], not to [[1.0]]
+
+        INPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+            A       -   matrix, array[N,N]
+            IsUpper -   (optional) storage type:
+                        * if True, symmetric matrix  A  is  given  by  its  upper
+                          triangle, and the lower triangle isnt used
+                        * if False, symmetric matrix  A  is  given  by  its lower
+                          triangle, and the upper triangle isnt used
+                        * if not given, both lower and upper  triangles  must  be
+                          filled.
+
+          -- ALGLIB --
+             Copyright 11.01.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minqpsetquadraticterm(minqpstate state,
+            double[,] a,
+            bool isupper)
+        {
+            int n = 0;
+
+            n = state.n;
+            alglib.ap.assert(alglib.ap.rows(a)>=n, "MinQPSetQuadraticTerm: Rows(A)<N");
+            alglib.ap.assert(alglib.ap.cols(a)>=n, "MinQPSetQuadraticTerm: Cols(A)<N");
+            alglib.ap.assert(apserv.isfinitertrmatrix(a, n, isupper), "MinQPSetQuadraticTerm: A contains infinite or NaN elements");
+            minqpsetquadratictermfast(state, a, isupper, 0.0);
+        }
+
+
+        /*************************************************************************
+        This function sets starting point for QP solver. It is useful to have
+        good initial approximation to the solution, because it will increase
+        speed of convergence and identification of active constraints.
+
+        INPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+            X       -   starting point, array[N].
+
+          -- ALGLIB --
+             Copyright 11.01.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minqpsetstartingpoint(minqpstate state,
+            double[] x)
+        {
+            int n = 0;
+
+            n = state.n;
+            alglib.ap.assert(alglib.ap.len(x)>=n, "MinQPSetStartingPoint: Length(B)<N");
+            alglib.ap.assert(apserv.isfinitevector(x, n), "MinQPSetStartingPoint: X contains infinite or NaN elements");
+            minqpsetstartingpointfast(state, x);
+        }
+
+
+        /*************************************************************************
+        This  function sets origin for QP solver. By default, following QP program
+        is solved:
+
+            min(0.5*x'*A*x+b'*x)
+            
+        This function allows to solve different problem:
+
+            min(0.5*(x-x_origin)'*A*(x-x_origin)+b'*(x-x_origin))
+            
+        INPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+            XOrigin -   origin, array[N].
+
+          -- ALGLIB --
+             Copyright 11.01.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minqpsetorigin(minqpstate state,
+            double[] xorigin)
+        {
+            int n = 0;
+
+            n = state.n;
+            alglib.ap.assert(alglib.ap.len(xorigin)>=n, "MinQPSetOrigin: Length(B)<N");
+            alglib.ap.assert(apserv.isfinitevector(xorigin, n), "MinQPSetOrigin: B contains infinite or NaN elements");
+            minqpsetoriginfast(state, xorigin);
+        }
+
+
+        /*************************************************************************
+        This function tells solver to use Cholesky-based algorithm.
+
+        Cholesky-based algorithm can be used when:
+        * problem is convex
+        * there is no constraints or only boundary constraints are present
+
+        This algorithm has O(N^3) complexity for unconstrained problem and  is  up
+        to several times slower on bound constrained  problems  (these  additional
+        iterations are needed to identify active constraints).
+
+        INPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+
+          -- ALGLIB --
+             Copyright 11.01.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minqpsetalgocholesky(minqpstate state)
+        {
+            state.algokind = 1;
+        }
+
+
+        /*************************************************************************
+        This function sets boundary constraints for QP solver
+
+        Boundary constraints are inactive by default (after initial creation).
+        After  being  set,  they  are  preserved  until explicitly turned off with
+        another SetBC() call.
+
+        INPUT PARAMETERS:
+            State   -   structure stores algorithm state
+            BndL    -   lower bounds, array[N].
+                        If some (all) variables are unbounded, you may specify
+                        very small number or -INF (latter is recommended because
+                        it will allow solver to use better algorithm).
+            BndU    -   upper bounds, array[N].
+                        If some (all) variables are unbounded, you may specify
+                        very large number or +INF (latter is recommended because
+                        it will allow solver to use better algorithm).
+                        
+        NOTE: it is possible to specify BndL[i]=BndU[i]. In this case I-th
+        variable will be "frozen" at X[i]=BndL[i]=BndU[i].
+
+          -- ALGLIB --
+             Copyright 11.01.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minqpsetbc(minqpstate state,
+            double[] bndl,
+            double[] bndu)
+        {
+            int i = 0;
+            int n = 0;
+
+            n = state.n;
+            alglib.ap.assert(alglib.ap.len(bndl)>=n, "MinQPSetBC: Length(BndL)<N");
+            alglib.ap.assert(alglib.ap.len(bndu)>=n, "MinQPSetBC: Length(BndU)<N");
+            for(i=0; i<=n-1; i++)
+            {
+                alglib.ap.assert(math.isfinite(bndl[i]) || Double.IsNegativeInfinity(bndl[i]), "MinQPSetBC: BndL contains NAN or +INF");
+                alglib.ap.assert(math.isfinite(bndu[i]) || Double.IsPositiveInfinity(bndu[i]), "MinQPSetBC: BndU contains NAN or -INF");
+                state.bndl[i] = bndl[i];
+                state.havebndl[i] = math.isfinite(bndl[i]);
+                state.bndu[i] = bndu[i];
+                state.havebndu[i] = math.isfinite(bndu[i]);
+            }
+        }
+
+
+        /*************************************************************************
+        This function solves quadratic programming problem.
+        You should call it after setting solver options with MinQPSet...() calls.
+
+        INPUT PARAMETERS:
+            State   -   algorithm state
+
+        You should use MinQPResults() function to access results after calls
+        to this function.
+
+          -- ALGLIB --
+             Copyright 11.01.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minqpoptimize(minqpstate state)
+        {
+            int n = 0;
+            int i = 0;
+            int j = 0;
+            int nbc = 0;
+            int nlc = 0;
+            double v0 = 0;
+            double v1 = 0;
+            double vn0 = 0;
+            bool constraintsactivated = new bool();
+            bool constraintsdeactivated = new bool();
+            bool noisystep = new bool();
+            double v = 0;
+            double noisetolerance = 0;
+            int i_ = 0;
+
+            noisetolerance = 10;
+            n = state.n;
+            state.repterminationtype = -5;
+            state.repinneriterationscount = 0;
+            state.repouteriterationscount = 0;
+            state.repncholesky = 0;
+            state.repnmv = 0;
+            
+            //
+            // check correctness of constraints
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                if( state.havebndl[i] && state.havebndu[i] )
+                {
+                    if( (double)(state.bndl[i])>(double)(state.bndu[i]) )
+                    {
+                        state.repterminationtype = -3;
+                        return;
+                    }
+                }
+            }
+            
+            //
+            // count number of bound and linear constraints
+            //
+            nbc = 0;
+            nlc = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                if( state.havebndl[i] )
+                {
+                    nbc = nbc+1;
+                }
+                if( state.havebndu[i] )
+                {
+                    nbc = nbc+1;
+                }
+            }
+            
+            //
+            // Our formulation of quadratic problem includes origin point,
+            // i.e. we have F(x-x_origin) which is minimized subject to
+            // constraints on x, instead of having simply F(x).
+            //
+            // Here we make transition from non-zero origin to zero one.
+            // In order to make such transition we have to:
+            // 1. subtract x_origin from x_start
+            // 2. modify constraints
+            // 3. solve problem
+            // 4. add x_origin to solution
+            //
+            // There is alternate solution - to modify quadratic function
+            // by expansion of multipliers containing (x-x_origin), but
+            // we prefer to modify constraints, because it is a) more precise
+            // and b) easier to to.
+            //
+            // Parts (1)-(2) are done here. After this block is over,
+            // we have:
+            // * XC, which stores shifted XStart (if we don't have XStart,
+            //   value of XC will be ignored later)
+            // * WorkBndL, WorkBndU, which store modified boundary constraints.
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                if( state.havebndl[i] )
+                {
+                    state.workbndl[i] = state.bndl[i]-state.xorigin[i];
+                }
+                else
+                {
+                    state.workbndl[i] = Double.NegativeInfinity;
+                }
+                if( state.havebndu[i] )
+                {
+                    state.workbndu[i] = state.bndu[i]-state.xorigin[i];
+                }
+                else
+                {
+                    state.workbndu[i] = Double.PositiveInfinity;
+                }
+            }
+            
+            //
+            // modify starting point XC according to boundary constraints
+            //
+            if( state.havex )
+            {
+                
+                //
+                // We have starting point in StartX, so we just have to shift and bound it
+                //
+                for(i=0; i<=n-1; i++)
+                {
+                    state.xc[i] = state.startx[i]-state.xorigin[i];
+                    if( state.havebndl[i] )
+                    {
+                        if( (double)(state.xc[i])<(double)(state.workbndl[i]) )
+                        {
+                            state.xc[i] = state.workbndl[i];
+                        }
+                    }
+                    if( state.havebndu[i] )
+                    {
+                        if( (double)(state.xc[i])>(double)(state.workbndu[i]) )
+                        {
+                            state.xc[i] = state.workbndu[i];
+                        }
+                    }
+                }
+            }
+            else
+            {
+                
+                //
+                // We don't have starting point, so we deduce it from
+                // constraints (if they are present).
+                //
+                // NOTE: XC contains some meaningless values from previous block
+                // which are ignored by code below.
+                //
+                for(i=0; i<=n-1; i++)
+                {
+                    if( state.havebndl[i] && state.havebndu[i] )
+                    {
+                        state.xc[i] = 0.5*(state.workbndl[i]+state.workbndu[i]);
+                        if( (double)(state.xc[i])<(double)(state.workbndl[i]) )
+                        {
+                            state.xc[i] = state.workbndl[i];
+                        }
+                        if( (double)(state.xc[i])>(double)(state.workbndu[i]) )
+                        {
+                            state.xc[i] = state.workbndu[i];
+                        }
+                        continue;
+                    }
+                    if( state.havebndl[i] )
+                    {
+                        state.xc[i] = state.workbndl[i];
+                        continue;
+                    }
+                    if( state.havebndu[i] )
+                    {
+                        state.xc[i] = state.workbndu[i];
+                        continue;
+                    }
+                    state.xc[i] = 0;
+                }
+            }
+            
+            //
+            // Select algo
+            //
+            if( state.algokind==1 && state.akind==0 )
+            {
+                
+                //
+                // Cholesky-based algorithm for dense bound constrained problems.
+                //
+                // This algorithm exists in two variants:
+                // * unconstrained one, which can solve problem using only one NxN
+                //   double matrix
+                // * bound constrained one, which needs two NxN matrices
+                //
+                // We will try to solve problem using unconstrained algorithm,
+                // and will use bound constrained version only when constraints
+                // are actually present
+                //
+                if( nbc==0 && nlc==0 )
+                {
+                    
+                    //
+                    // "Simple" unconstrained version
+                    //
+                    apserv.rvectorsetlengthatleast(ref state.tmp0, n);
+                    apserv.rvectorsetlengthatleast(ref state.bufb, n);
+                    apserv.rmatrixsetlengthatleast(ref state.bufa, n, n);
+                    for(i=0; i<=n-1; i++)
+                    {
+                        for(j=i; j<=n-1; j++)
+                        {
+                            state.bufa[i,j] = state.densea[i,j];
+                        }
+                    }
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        state.bufb[i_] = state.b[i_];
+                    }
+                    state.repncholesky = 1;
+                    if( !trfac.spdmatrixcholeskyrec(ref state.bufa, 0, n, true, ref state.tmp0) )
+                    {
+                        state.repterminationtype = -5;
+                        return;
+                    }
+                    fbls.fblscholeskysolve(state.bufa, 1.0, n, true, ref state.bufb, ref state.tmp0);
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        state.xc[i_] = -state.bufb[i_];
+                    }
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        state.xc[i_] = state.xc[i_] + state.xorigin[i_];
+                    }
+                    state.repouteriterationscount = 1;
+                    state.repterminationtype = 4;
+                    return;
+                }
+                alglib.ap.assert(nlc==0, "MinQPOptimize: internal error");
+                
+                //
+                // BoxQP algorithm
+                //
+                apserv.rvectorsetlengthatleast(ref state.tmp0, n);
+                apserv.rmatrixsetlengthatleast(ref state.bufa, n, n);
+                while( true )
+                {
+                    
+                    //
+                    // Phase 1.
+                    // 
+                    // Determine active set using only current point location
+                    // (we ignore antigradient direction, constraint is activated
+                    // when current point is located at the bound of the area).
+                    //
+                    // This phase allows us to reduce function value without
+                    // leaving current face.
+                    //
+                    for(i=0; i<=n-1; i++)
+                    {
+                        state.activeset[i] = (state.havebndl[i] && (double)(state.xc[i])==(double)(state.workbndl[i])) || (state.havebndu[i] && (double)(state.xc[i])==(double)(state.workbndu[i]));
+                    }
+                    state.repncholesky = state.repncholesky+1;
+                    if( !minqppreparenewtonstep(state, state.xc, state.activeset, ref state.xn, n, ref state.bufa, ref state.tmp0, ref state.tmp1) )
+                    {
+                        state.repterminationtype = -5;
+                        return;
+                    }
+                    minqpstepto(state, state.xc, ref state.xn, state.workbndl, state.havebndl, state.workbndu, state.havebndu, n, ref state.tmp0, ref state.tmp1, ref constraintsactivated, ref constraintsdeactivated, ref noisystep);
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        state.xc[i_] = state.xn[i_];
+                    }
+                    
+                    //
+                    // Phase 2.
+                    // 
+                    // Determine active set using current point location and
+                    // antigradient direction. Then perform steepest descent step.
+                    //
+                    // 1. we calculate projected gradient PG and scale using
+                    //    diagonal of Hessian matrix
+                    // 2. we calculate Cauchy point XN
+                    // 3. in case difference between f(XN) and f(XC) is dominated by
+                    //    numerical noise, we terminate algorithm.
+                    // 4. otherwise, we perform bounded step to XN
+                    //
+                    // This is the only phase were we can terminate algorithm
+                    //
+                    // NOTE: Cauchy point is calculated as minimizer of
+                    //       g(t) = f(xc-t*pg) = t^2*(0.5*pg'*A*pg) + t*(-pg*g) + const
+                    //       where pg is a projected gradient, t is a step length, g is
+                    //       a non-projected gradient.
+                    //
+                    // NOTE 2: here we assume that our problem is convex, hence pg'*A*pg>=0,
+                    //         and pg'*A*pg=0 if and only if pg=0.
+                    //
+                    minqpgrad(state, ref state.xc, ref state.gc);
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        state.pg[i_] = state.gc[i_];
+                    }
+                    minqpscalegrad(state, ref state.pg);
+                    optserv.projectgradientintobc(state.xc, ref state.pg, state.workbndl, state.havebndl, state.workbndu, state.havebndu, n, 0);
+                    minqptargetfunction(state, state.xc, ref v, ref vn0);
+                    v0 = minqpxtax2(state, state.pg);
+                    if( (double)(v0)>(double)(0) )
+                    {
+                        
+                        //
+                        // Function curvature is positive.
+                        // Calculate predicted function decrease, which is equal to (pg'*g)^2/(4*pg'*A*pg).
+                        // In case step is dominated by noise, we exit.
+                        //
+                        v1 = 0.0;
+                        for(i_=0; i_<=n-1;i_++)
+                        {
+                            v1 += state.pg[i_]*state.gc[i_];
+                        }
+                        if( (double)(math.sqr(v1)/(4*v0))<(double)(noisetolerance*vn0) )
+                        {
+                            break;
+                        }
+                        v = v1/(2*v0);
+                        for(i_=0; i_<=n-1;i_++)
+                        {
+                            state.xn[i_] = state.xc[i_];
+                        }
+                        for(i_=0; i_<=n-1;i_++)
+                        {
+                            state.xn[i_] = state.xn[i_] - v*state.pg[i_];
+                        }
+                        minqpstepto(state, state.xc, ref state.xn, state.workbndl, state.havebndl, state.workbndu, state.havebndu, n, ref state.tmp0, ref state.tmp1, ref constraintsactivated, ref constraintsdeactivated, ref noisystep);
+                        for(i_=0; i_<=n-1;i_++)
+                        {
+                            state.xc[i_] = state.xn[i_];
+                        }
+                    }
+                    else
+                    {
+                        
+                        //
+                        // non-positive curvature detected.
+                        // because it is assumed that function is positive definite,
+                        // it means that projected gradient is zero (termination is needed).
+                        //
+                        break;
+                    }
+                    
+                    //
+                    // Phase 3.
+                    // 
+                    // Determine active set using current point location and
+                    // antigradient direction. Then perform Newton step.
+                    //
+                    // This phase allows us to leave current face.
+                    //
+                    minqpgrad(state, ref state.xc, ref state.gc);
+                    for(i=0; i<=n-1; i++)
+                    {
+                        state.activeset[i] = false;
+                        state.activeset[i] = state.activeset[i] || ((state.havebndl[i] && (double)(state.xc[i])==(double)(state.workbndl[i])) && (double)(state.gc[i])>(double)(0));
+                        state.activeset[i] = state.activeset[i] || ((state.havebndu[i] && (double)(state.xc[i])==(double)(state.workbndu[i])) && (double)(state.gc[i])<(double)(0));
+                    }
+                    if( !minqppreparenewtonstep(state, state.xc, state.activeset, ref state.xn, n, ref state.bufa, ref state.tmp0, ref state.tmp1) )
+                    {
+                        state.repterminationtype = -5;
+                        return;
+                    }
+                    state.repncholesky = state.repncholesky+1;
+                    minqpstepto(state, state.xc, ref state.xn, state.workbndl, state.havebndl, state.workbndu, state.havebndu, n, ref state.tmp0, ref state.tmp1, ref constraintsactivated, ref constraintsdeactivated, ref noisystep);
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        state.xc[i_] = state.xn[i_];
+                    }
+                }
+                
+                //
+                // Post-process: add XOrigin to XC
+                //
+                for(i=0; i<=n-1; i++)
+                {
+                    if( state.havebndl[i] && (double)(state.xc[i])==(double)(state.workbndl[i]) )
+                    {
+                        state.xc[i] = state.bndl[i];
+                        continue;
+                    }
+                    if( state.havebndu[i] && (double)(state.xc[i])==(double)(state.workbndu[i]) )
+                    {
+                        state.xc[i] = state.bndu[i];
+                        continue;
+                    }
+                    state.xc[i] = apserv.boundval(state.xc[i]+state.xorigin[i], state.bndl[i], state.bndu[i]);
+                }
+                state.repterminationtype = 4;
+                return;
+            }
+        }
+
+
+        /*************************************************************************
+        QP solver results
+
+        INPUT PARAMETERS:
+            State   -   algorithm state
+
+        OUTPUT PARAMETERS:
+            X       -   array[0..N-1], solution
+            Rep     -   optimization report. You should check Rep.TerminationType,
+                        which contains completion code, and you may check  another
+                        fields which contain another information  about  algorithm
+                        functioning.
+
+          -- ALGLIB --
+             Copyright 11.01.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minqpresults(minqpstate state,
+            ref double[] x,
+            minqpreport rep)
+        {
+            x = new double[0];
+
+            minqpresultsbuf(state, ref x, rep);
+        }
+
+
+        /*************************************************************************
+        QP results
+
+        Buffered implementation of MinQPResults() which uses pre-allocated  buffer
+        to store X[]. If buffer size is  too  small,  it  resizes  buffer.  It  is
+        intended to be used in the inner cycles of performance critical algorithms
+        where array reallocation penalty is too large to be ignored.
+
+          -- ALGLIB --
+             Copyright 11.01.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minqpresultsbuf(minqpstate state,
+            ref double[] x,
+            minqpreport rep)
+        {
+            int i_ = 0;
+
+            if( alglib.ap.len(x)<state.n )
+            {
+                x = new double[state.n];
+            }
+            for(i_=0; i_<=state.n-1;i_++)
+            {
+                x[i_] = state.xc[i_];
+            }
+            rep.inneriterationscount = state.repinneriterationscount;
+            rep.outeriterationscount = state.repouteriterationscount;
+            rep.nmv = state.repnmv;
+            rep.ncholesky = state.repncholesky;
+            rep.terminationtype = state.repterminationtype;
+        }
+
+
+        /*************************************************************************
+        Fast version of MinQPSetLinearTerm(), which doesn't check its arguments.
+        For internal use only.
+
+          -- ALGLIB --
+             Copyright 11.01.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minqpsetlineartermfast(minqpstate state,
+            double[] b)
+        {
+            int n = 0;
+            int i_ = 0;
+
+            n = state.n;
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.b[i_] = b[i_];
+            }
+        }
+
+
+        /*************************************************************************
+        Fast version of MinQPSetQuadraticTerm(), which doesn't check its arguments.
+
+        It accepts additional parameter - shift S, which allows to "shift"  matrix
+        A by adding s*I to A. S must be positive (although it is not checked).
+
+        For internal use only.
+
+          -- ALGLIB --
+             Copyright 11.01.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minqpsetquadratictermfast(minqpstate state,
+            double[,] a,
+            bool isupper,
+            double s)
+        {
+            int k = 0;
+            int n = 0;
+            int i_ = 0;
+
+            n = state.n;
+            state.akind = 0;
+            apserv.rmatrixsetlengthatleast(ref state.densea, n, n);
+            if( isupper )
+            {
+                for(k=0; k<=n-2; k++)
+                {
+                    state.densea[k,k] = a[k,k]+s;
+                    for(i_=k+1; i_<=n-1;i_++)
+                    {
+                        state.densea[i_,k] = a[k,i_];
+                    }
+                    for(i_=k+1; i_<=n-1;i_++)
+                    {
+                        state.densea[k,i_] = a[k,i_];
+                    }
+                }
+                state.densea[n-1,n-1] = a[n-1,n-1]+s;
+            }
+            else
+            {
+                state.densea[0,0] = a[0,0]+s;
+                for(k=1; k<=n-1; k++)
+                {
+                    for(i_=0; i_<=k-1;i_++)
+                    {
+                        state.densea[k,i_] = a[k,i_];
+                    }
+                    for(i_=0; i_<=k-1;i_++)
+                    {
+                        state.densea[i_,k] = a[k,i_];
+                    }
+                    state.densea[k,k] = a[k,k]+s;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Interna lfunction which allows to rewrite diagonal of quadratic term.
+        For internal use only.
+
+        This function can be used only when you have dense A and already made
+        MinQPSetQuadraticTerm(Fast) call.
+
+          -- ALGLIB --
+             Copyright 16.01.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minqprewritediagonal(minqpstate state,
+            double[] s)
+        {
+            int k = 0;
+            int n = 0;
+
+            alglib.ap.assert(state.akind==0, "MinQPRewriteDiagonal: internal error (AKind<>0)");
+            n = state.n;
+            for(k=0; k<=n-1; k++)
+            {
+                state.densea[k,k] = s[k];
+            }
+        }
+
+
+        /*************************************************************************
+        Fast version of MinQPSetStartingPoint(), which doesn't check its arguments.
+        For internal use only.
+
+          -- ALGLIB --
+             Copyright 11.01.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minqpsetstartingpointfast(minqpstate state,
+            double[] x)
+        {
+            int n = 0;
+            int i_ = 0;
+
+            n = state.n;
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.startx[i_] = x[i_];
+            }
+            state.havex = true;
+        }
+
+
+        /*************************************************************************
+        Fast version of MinQPSetOrigin(), which doesn't check its arguments.
+        For internal use only.
+
+          -- ALGLIB --
+             Copyright 11.01.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minqpsetoriginfast(minqpstate state,
+            double[] xorigin)
+        {
+            int n = 0;
+            int i_ = 0;
+
+            n = state.n;
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.xorigin[i_] = xorigin[i_];
+            }
+        }
+
+
+        /*************************************************************************
+        This  function  calculates gradient of quadratic function at XC and stores
+        it in the GC.
+
+          -- ALGLIB --
+             Copyright 11.01.2011 by Bochkanov Sergey
+        *************************************************************************/
+        private static void minqpgrad(minqpstate state,
+            ref double[] xc,
+            ref double[] gc)
+        {
+            int n = 0;
+            int i = 0;
+            double v = 0;
+            int i_ = 0;
+
+            n = state.n;
+            alglib.ap.assert(state.akind==-1 || state.akind==0, "MinQPGrad: internal error");
+            
+            //
+            // zero A
+            //
+            if( state.akind==-1 )
+            {
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    gc[i_] = state.b[i_];
+                }
+                return;
+            }
+            
+            //
+            // dense A
+            //
+            if( state.akind==0 )
+            {
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    gc[i_] = state.b[i_];
+                }
+                gc[0] = gc[0]+state.densea[0,0]*xc[0];
+                for(i=1; i<=n-1; i++)
+                {
+                    v = 0.0;
+                    for(i_=0; i_<=i-1;i_++)
+                    {
+                        v += state.densea[i,i_]*xc[i_];
+                    }
+                    gc[i] = gc[i]+v+state.densea[i,i]*xc[i];
+                    v = xc[i];
+                    for(i_=0; i_<=i-1;i_++)
+                    {
+                        gc[i_] = gc[i_] + v*state.densea[i,i_];
+                    }
+                }
+                return;
+            }
+        }
+
+
+        /*************************************************************************
+        This function scales gradient by multiplying it by inverse of the diagonal
+        of the Hessian matrix. It should be used to  accelerate  steepest  descent
+        phase of the QP solver.
+
+        Although  it  is  called  "scale-grad",  it  can be called for any vector,
+        whether it is gradient, anti-gradient, or just some vector.
+
+        It MUST be called only for problems with SPD matrices.
+
+          -- ALGLIB --
+             Copyright 11.01.2011 by Bochkanov Sergey
+        *************************************************************************/
+        private static void minqpscalegrad(minqpstate state,
+            ref double[] g)
+        {
+            int n = 0;
+            int i = 0;
+
+            n = state.n;
+            alglib.ap.assert(state.akind==0, "MinQPGrad: internal error");
+            
+            //
+            // dense A
+            //
+            if( state.akind==0 )
+            {
+                for(i=0; i<=n-1; i++)
+                {
+                    g[i] = g[i]/state.densea[i,i];
+                }
+                return;
+            }
+        }
+
+
+        /*************************************************************************
+        This  function  calculates 0.5*x'*A*x for given X.
+
+          -- ALGLIB --
+             Copyright 11.01.2011 by Bochkanov Sergey
+        *************************************************************************/
+        private static double minqpxtax2(minqpstate state,
+            double[] x)
+        {
+            double result = 0;
+            int n = 0;
+            int i = 0;
+            int j = 0;
+
+            n = state.n;
+            alglib.ap.assert(state.akind==-1 || state.akind==0, "MinQPXTAX: internal error");
+            result = 0;
+            
+            //
+            // zero A
+            //
+            if( state.akind==-1 )
+            {
+                result = 0.0;
+                return result;
+            }
+            
+            //
+            // dense A
+            //
+            if( state.akind==0 )
+            {
+                result = 0;
+                for(i=0; i<=n-1; i++)
+                {
+                    for(j=0; j<=i-1; j++)
+                    {
+                        result = result+state.densea[i,j]*x[i]*x[j];
+                    }
+                    result = result+0.5*state.densea[i,i]*math.sqr(x[i]);
+                }
+                return result;
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function calculates 0.5*x'*A*x+b*x for given X.
+
+        INPUT PARAMETERS:
+            State   -   quadratic model
+            X       -   point of calculation
+            
+        OUTPUT PARAMETERS:
+            F       -   function value
+            Noise   -   estimate of the numerical noise level
+
+          -- ALGLIB --
+             Copyright 11.01.2011 by Bochkanov Sergey
+        *************************************************************************/
+        private static void minqptargetfunction(minqpstate state,
+            double[] x,
+            ref double f,
+            ref double noise)
+        {
+            int n = 0;
+            int i = 0;
+            int j = 0;
+            double v = 0;
+
+            f = 0;
+            noise = 0;
+
+            n = state.n;
+            alglib.ap.assert(state.akind==-1 || state.akind==0, "MinQPXTAX: internal error");
+            
+            //
+            // zero A
+            //
+            if( state.akind==-1 )
+            {
+                f = 0.0;
+                noise = 0.0;
+                
+                //
+                // Linear term
+                //
+                for(i=0; i<=n-1; i++)
+                {
+                    v = state.b[i]*x[i];
+                    f = f+v;
+                    noise = Math.Max(noise, Math.Abs(v));
+                }
+                
+                //
+                // End
+                //
+                noise = math.machineepsilon*n*noise;
+                return;
+            }
+            
+            //
+            // dense A
+            //
+            if( state.akind==0 )
+            {
+                f = 0;
+                noise = 0;
+                
+                //
+                // Quadratic term
+                //
+                for(i=0; i<=n-1; i++)
+                {
+                    for(j=0; j<=i-1; j++)
+                    {
+                        v = state.densea[i,j]*x[i]*x[j];
+                        f = f+v;
+                        noise = Math.Max(noise, Math.Abs(v));
+                    }
+                    v = 0.5*state.densea[i,i]*math.sqr(x[i]);
+                    f = f+v;
+                    noise = Math.Max(noise, Math.Abs(v));
+                }
+                
+                //
+                // Linear term
+                //
+                for(i=0; i<=n-1; i++)
+                {
+                    v = state.b[i]*x[i];
+                    f = f+v;
+                    noise = Math.Max(noise, Math.Abs(v));
+                }
+                
+                //
+                // End
+                //
+                noise = math.machineepsilon*n*noise;
+                return;
+            }
+        }
+
+
+        /*************************************************************************
+        This function calculates Newton step subject to the set of the active
+        equality constraints.
+
+        INPUT PARAMETERS:
+            State   -   MinQP state. This structure is used ONLY to get quadratic
+                        model. This function does NOT use  other  fields  of  the
+                        State structure (like temporary buffers or bounds).
+            XC      -   current point
+            ActiveSet-  set of active equality constraints
+            XN      -   preallocated buffer
+            N       -   problem size
+            BufA    -   temporary buffer (automatically reallocated, if needed)
+            BufB    -   temporary buffer (automatically reallocated, if needed)
+
+        OUTPUT PARAMETERS:
+            XN      -   minimizer  of  the  quadratic  model  subject to equality
+                        constraints. This point can be infeasible because full
+                        Newton step may lead us to infeasible area.
+
+        RESULT:
+            True, when Newton step was correctly calculated
+            False, when this function was called for non-convex problem (matrix
+            is not positive definite, Cholesky factorization failed)
+                        
+        NOTE: this function correctly works when all constraints are active.
+
+          -- ALGLIB --
+             Copyright 29.02.2012 by Bochkanov Sergey
+        *************************************************************************/
+        private static bool minqppreparenewtonstep(minqpstate state,
+            double[] xc,
+            bool[] activeset,
+            ref double[] xn,
+            int n,
+            ref double[,] bufa,
+            ref double[] bufb,
+            ref double[] bufc)
+        {
+            bool result = new bool();
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int nfree = 0;
+
+            alglib.ap.assert(state.akind==0, "MinQPNewtonStep: unexpected model type");
+            
+            //
+            // Determine XN - minimizer of the quadratic function subject to active constraints.
+            // It is done in two substeps:
+            // * first, we determine values of the constrained components
+            // * then we determine unconstrained components by splitting x'*A*x+b'*x into
+            //               ( A0  A1 ) ( x0 )              ( x0 )
+            //   ( x0' x1' )*(        )*(    ) + (b0' b1')* (    ), with fixed x1 and "free" x0,
+            //               ( A1' A2 ) ( x1 )              ( x1 )
+            //   and seeking for minimizer x0_min = inv(A0)*(-A1*x1-b0). We store A0 into BufA,
+            //   and -A1*x1-b0 is stored into BufB. We make implicit permutation of variables
+            //   when storing these matrices/vectors and retreiving result into XN.
+            //
+            apserv.rmatrixsetlengthatleast(ref bufa, n, n);
+            apserv.rvectorsetlengthatleast(ref bufb, n);
+            for(i=0; i<=n-1; i++)
+            {
+                if( activeset[i] )
+                {
+                    xn[i] = xc[i];
+                }
+            }
+            nfree = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                if( !activeset[i] )
+                {
+                    bufb[nfree] = -state.b[i];
+                    k = 0;
+                    for(j=0; j<=n-1; j++)
+                    {
+                        if( !activeset[j] )
+                        {
+                            bufa[nfree,k] = state.densea[i,j];
+                            k = k+1;
+                        }
+                        else
+                        {
+                            bufb[nfree] = bufb[nfree]-state.densea[i,j]*xn[j];
+                        }
+                    }
+                    nfree = nfree+1;
+                }
+            }
+            if( nfree>0 )
+            {
+                if( !trfac.spdmatrixcholeskyrec(ref bufa, 0, nfree, true, ref bufc) )
+                {
+                    result = false;
+                    return result;
+                }
+                fbls.fblscholeskysolve(bufa, 1.0, nfree, true, ref bufb, ref bufc);
+                k = 0;
+                for(i=0; i<=n-1; i++)
+                {
+                    if( !activeset[i] )
+                    {
+                        xn[i] = bufb[k];
+                        k = k+1;
+                    }
+                }
+            }
+            result = true;
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function performs step from XC to XN subject to bound constraints and
+        on  condition  that  function  value  decreases. User prepares two points:
+        * XC, current point, feasible w.r.t. bound constraints
+        * XN, candidate point, which minimizes function value in some sense (either
+          it is Newton  minimizer, or minimizer along steepest descent  direction),
+          subject to equality constraints active at XC. It can be infeasible w.r.t.
+          other  constraints  -  ones  which were not active at XC.
+
+        It is important that function MUST monotonically decrease all the way from
+        XC to XN.
+
+        This function:
+        1. compares quadratic model values at XN and XC; if f(XN)>=f(XC), it sets
+           NoisyStep, sets XN to XC and returns
+        2. performs longest step from XC to XN until we activate constraints
+        3. handles the rest of the step  by  performing  Armijo  line  search  for
+           f()=PROJ(XC_new+alpha*(XN-XC_new)) where  PROJ()  is  a  projection  on
+           bound constrained area, alpha is a  search  parameter  (we  start  from
+           alpha=1 and decrease it), and XC_new is a new point obtained at step 2.
+        4. New location is saved into XN. In case some constraints were  activated
+           or deactivated, we set corresponding flag variable.  In  case  function
+           value  decrease  is  dominated by the numerical noise, we set NoisyStep
+           flag.
+
+        INPUT PARAMETERS:
+            State   -   MinQP state. This structure is used ONLY to get quadratic
+                        model. This function does NOT use  other  fields  of  the
+                        State structure (like temporary buffers or bounds).
+            XC      -   current point, must be feasible w.r.t. constraints
+            XN      -   candidate point, can be infeasible
+            BndL    -   lower bounds, array[N]
+                        (may contain -INF, when bound is not present)
+            HaveBndL-   array[N], if HaveBndL[i] is False,
+                        then i-th bound is not present
+            BndU    -   array[NMain], upper bounds
+                        (may contain +INF, when bound is not present)
+            HaveBndU-   array[NMain], if HaveBndU[i] is False,
+                        then i-th bound is not present
+            N       -   problem size
+            Tmp0    -   temporary buffer (automatically reallocated if needed)
+
+        OUTPUT PARAMETERS:
+            XN      -   new point, feasible w.r.t. bound constraints
+          
+        NOTE 1: it is important that XN is constrained only by constraints,  which
+                are active at XC. Any attempt to introduce additional  constraints
+                may lead to algorithm stagnation
+
+          -- ALGLIB --
+             Copyright 29.02.2012 by Bochkanov Sergey
+        *************************************************************************/
+        private static void minqpstepto(minqpstate state,
+            double[] xc,
+            ref double[] xn,
+            double[] bndl,
+            bool[] havebndl,
+            double[] bndu,
+            bool[] havebndu,
+            int n,
+            ref double[] tmp0,
+            ref double[] tmp1,
+            ref bool constraintsactivated,
+            ref bool constraintsdeactivated,
+            ref bool noisystep)
+        {
+            int variabletofreeze = 0;
+            double valuetofreeze = 0;
+            double maxsteplen = 0;
+            double armijostep = 0;
+            int armijocnt = 0;
+            double v = 0;
+            double v0 = 0;
+            double v1 = 0;
+            double vn0 = 0;
+            double vn1 = 0;
+            bool b0 = new bool();
+            bool b1 = new bool();
+            int i = 0;
+            int i_ = 0;
+
+            constraintsactivated = new bool();
+            constraintsdeactivated = new bool();
+            noisystep = new bool();
+
+            apserv.rvectorsetlengthatleast(ref tmp0, n);
+            apserv.rvectorsetlengthatleast(ref tmp1, n);
+            
+            //
+            // Compare function values at XC and XN
+            //
+            minqptargetfunction(state, xc, ref v0, ref v);
+            minqptargetfunction(state, xn, ref v1, ref v);
+            if( (double)(v1)>=(double)(v0) )
+            {
+                constraintsactivated = false;
+                constraintsdeactivated = false;
+                noisystep = true;
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    xn[i_] = xc[i_];
+                }
+                return;
+            }
+            
+            //
+            // Perform bounded step (we activate at most one constraint during this phase)
+            //
+            for(i_=0; i_<=n-1;i_++)
+            {
+                tmp0[i_] = xn[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                tmp0[i_] = tmp0[i_] - xc[i_];
+            }
+            optserv.calculatestepbound(xc, tmp0, 1.0, bndl, havebndl, bndu, havebndu, n, 0, ref variabletofreeze, ref valuetofreeze, ref maxsteplen);
+            if( variabletofreeze>=0 && (double)(maxsteplen)<=(double)(1) )
+            {
+                
+                //
+                // Perform bounded step
+                //
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    tmp0[i_] = xn[i_];
+                }
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    tmp0[i_] = tmp0[i_] - xc[i_];
+                }
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    tmp0[i_] = maxsteplen*tmp0[i_];
+                }
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    tmp0[i_] = tmp0[i_] + xc[i_];
+                }
+                optserv.postprocessboundedstep(ref tmp0, xc, bndl, havebndl, bndu, havebndu, n, 0, variabletofreeze, valuetofreeze, maxsteplen, maxsteplen);
+            }
+            else
+            {
+                
+                //
+                // Perform full step
+                //
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    tmp0[i_] = xn[i_];
+                }
+            }
+            
+            //
+            // Tmp0 contains either XN - or intermediate point halfway between XC and XN.
+            // In case it contains intermediate point, handle the rest using Armijo search and projections.
+            //
+            if( variabletofreeze>=0 && (double)(maxsteplen)<=(double)(1) )
+            {
+                
+                //
+                // Armijo search.
+                // After this block Tmp1 contains best point found so far (can be equal to Tmp0).
+                //
+                minqptargetfunction(state, tmp0, ref v0, ref v);
+                armijostep = 1.0;
+                armijocnt = 5;
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    tmp1[i_] = tmp0[i_];
+                }
+                while( armijocnt>0 )
+                {
+                    
+                    //
+                    // Store candidate point in Tmp1
+                    //
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        tmp1[i_] = xn[i_];
+                    }
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        tmp1[i_] = tmp1[i_] - tmp0[i_];
+                    }
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        tmp1[i_] = armijostep*tmp1[i_];
+                    }
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        tmp1[i_] = tmp1[i_] + tmp0[i_];
+                    }
+                    for(i=0; i<=n-1; i++)
+                    {
+                        if( havebndl[i] && (double)(tmp1[i])<(double)(bndl[i]) )
+                        {
+                            tmp1[i] = bndl[i];
+                        }
+                        if( havebndu[i] && (double)(tmp1[i])>(double)(bndu[i]) )
+                        {
+                            tmp1[i] = bndu[i];
+                        }
+                    }
+                    
+                    //
+                    // Evaluate function
+                    //
+                    minqptargetfunction(state, tmp1, ref v1, ref v);
+                    if( (double)(v1)>=(double)(v0) )
+                    {
+                        for(i_=0; i_<=n-1;i_++)
+                        {
+                            tmp1[i_] = tmp0[i_];
+                        }
+                    }
+                    else
+                    {
+                        break;
+                    }
+                    
+                    //
+                    // Next iteration
+                    //
+                    armijocnt = armijocnt-1;
+                    armijostep = 0.5*armijostep;
+                }
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    xn[i_] = tmp1[i_];
+                }
+            }
+            else
+            {
+                
+                //
+                // No Armijo search
+                //
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    xn[i_] = tmp0[i_];
+                }
+            }
+            
+            //
+            //
+            // Check constraints status
+            //
+            constraintsactivated = false;
+            constraintsdeactivated = false;
+            for(i=0; i<=n-1; i++)
+            {
+                b0 = (havebndl[i] && (double)(bndl[i])==(double)(xc[i])) || (havebndu[i] && (double)(bndu[i])==(double)(xc[i]));
+                b1 = (havebndl[i] && (double)(bndl[i])==(double)(xn[i])) || (havebndu[i] && (double)(bndu[i])==(double)(xn[i]));
+                constraintsactivated = constraintsactivated || (b1 && !b0);
+                constraintsdeactivated = constraintsdeactivated || (b0 && !b1);
+            }
+            minqptargetfunction(state, xc, ref v0, ref vn0);
+            minqptargetfunction(state, xn, ref v1, ref vn1);
+            noisystep = (double)(v0-v1)<(double)(Math.Max(vn0, vn1));
+        }
+
+
+    }
+    public class minlm
+    {
+        /*************************************************************************
+        Levenberg-Marquardt optimizer.
+
+        This structure should be created using one of the MinLMCreate???()
+        functions. You should not access its fields directly; use ALGLIB functions
+        to work with it.
+        *************************************************************************/
+        public class minlmstate
+        {
+            public int n;
+            public int m;
+            public double diffstep;
+            public double epsg;
+            public double epsf;
+            public double epsx;
+            public int maxits;
+            public bool xrep;
+            public double stpmax;
+            public int maxmodelage;
+            public bool makeadditers;
+            public double[] x;
+            public double f;
+            public double[] fi;
+            public double[,] j;
+            public double[,] h;
+            public double[] g;
+            public bool needf;
+            public bool needfg;
+            public bool needfgh;
+            public bool needfij;
+            public bool needfi;
+            public bool xupdated;
+            public int algomode;
+            public bool hasf;
+            public bool hasfi;
+            public bool hasg;
+            public double[] xbase;
+            public double fbase;
+            public double[] fibase;
+            public double[] gbase;
+            public double[,] quadraticmodel;
+            public double[] bndl;
+            public double[] bndu;
+            public bool[] havebndl;
+            public bool[] havebndu;
+            public double[] s;
+            public double lambdav;
+            public double nu;
+            public int modelage;
+            public double[] xdir;
+            public double[] deltax;
+            public double[] deltaf;
+            public bool deltaxready;
+            public bool deltafready;
+            public int repiterationscount;
+            public int repterminationtype;
+            public int repnfunc;
+            public int repnjac;
+            public int repngrad;
+            public int repnhess;
+            public int repncholesky;
+            public rcommstate rstate;
+            public double[] choleskybuf;
+            public double[] tmp0;
+            public double actualdecrease;
+            public double predicteddecrease;
+            public double xm1;
+            public double xp1;
+            public double[] fm1;
+            public double[] fp1;
+            public minlbfgs.minlbfgsstate internalstate;
+            public minlbfgs.minlbfgsreport internalrep;
+            public minqp.minqpstate qpstate;
+            public minqp.minqpreport qprep;
+            public minlmstate()
+            {
+                x = new double[0];
+                fi = new double[0];
+                j = new double[0,0];
+                h = new double[0,0];
+                g = new double[0];
+                xbase = new double[0];
+                fibase = new double[0];
+                gbase = new double[0];
+                quadraticmodel = new double[0,0];
+                bndl = new double[0];
+                bndu = new double[0];
+                havebndl = new bool[0];
+                havebndu = new bool[0];
+                s = new double[0];
+                xdir = new double[0];
+                deltax = new double[0];
+                deltaf = new double[0];
+                rstate = new rcommstate();
+                choleskybuf = new double[0];
+                tmp0 = new double[0];
+                fm1 = new double[0];
+                fp1 = new double[0];
+                internalstate = new minlbfgs.minlbfgsstate();
+                internalrep = new minlbfgs.minlbfgsreport();
+                qpstate = new minqp.minqpstate();
+                qprep = new minqp.minqpreport();
+            }
+        };
+
+
+        /*************************************************************************
+        Optimization report, filled by MinLMResults() function
+
+        FIELDS:
+        * TerminationType, completetion code:
+            * -9    derivative correctness check failed;
+                    see Rep.WrongNum, Rep.WrongI, Rep.WrongJ for
+                    more information.
+            *  1    relative function improvement is no more than
+                    EpsF.
+            *  2    relative step is no more than EpsX.
+            *  4    gradient is no more than EpsG.
+            *  5    MaxIts steps was taken
+            *  7    stopping conditions are too stringent,
+                    further improvement is impossible
+        * IterationsCount, contains iterations count
+        * NFunc, number of function calculations
+        * NJac, number of Jacobi matrix calculations
+        * NGrad, number of gradient calculations
+        * NHess, number of Hessian calculations
+        * NCholesky, number of Cholesky decomposition calculations
+        *************************************************************************/
+        public class minlmreport
+        {
+            public int iterationscount;
+            public int terminationtype;
+            public int nfunc;
+            public int njac;
+            public int ngrad;
+            public int nhess;
+            public int ncholesky;
+        };
+
+
+
+
+        public const int lmmodefj = 0;
+        public const int lmmodefgj = 1;
+        public const int lmmodefgh = 2;
+        public const int lmflagnoprelbfgs = 1;
+        public const int lmflagnointlbfgs = 2;
+        public const int lmprelbfgsm = 5;
+        public const int lmintlbfgsits = 5;
+        public const int lbfgsnorealloc = 1;
+        public const double lambdaup = 2.0;
+        public const double lambdadown = 0.33;
+        public const double suspiciousnu = 16;
+        public const int smallmodelage = 3;
+        public const int additers = 5;
+
+
+        /*************************************************************************
+                        IMPROVED LEVENBERG-MARQUARDT METHOD FOR
+                         NON-LINEAR LEAST SQUARES OPTIMIZATION
+
+        DESCRIPTION:
+        This function is used to find minimum of function which is represented  as
+        sum of squares:
+            F(x) = f[0]^2(x[0],...,x[n-1]) + ... + f[m-1]^2(x[0],...,x[n-1])
+        using value of function vector f[] and Jacobian of f[].
+
+
+        REQUIREMENTS:
+        This algorithm will request following information during its operation:
+
+        * function vector f[] at given point X
+        * function vector f[] and Jacobian of f[] (simultaneously) at given point
+
+        There are several overloaded versions of  MinLMOptimize()  function  which
+        correspond  to  different LM-like optimization algorithms provided by this
+        unit. You should choose version which accepts fvec()  and jac() callbacks.
+        First  one  is used to calculate f[] at given point, second one calculates
+        f[] and Jacobian df[i]/dx[j].
+
+        You can try to initialize MinLMState structure with VJ  function and  then
+        use incorrect version  of  MinLMOptimize()  (for  example,  version  which
+        works  with  general  form function and does not provide Jacobian), but it
+        will  lead  to  exception  being  thrown  after first attempt to calculate
+        Jacobian.
+
+
+        USAGE:
+        1. User initializes algorithm state with MinLMCreateVJ() call
+        2. User tunes solver parameters with MinLMSetCond(),  MinLMSetStpMax() and
+           other functions
+        3. User calls MinLMOptimize() function which  takes algorithm  state   and
+           callback functions.
+        4. User calls MinLMResults() to get solution
+        5. Optionally, user may call MinLMRestartFrom() to solve  another  problem
+           with same N/M but another starting point and/or another function.
+           MinLMRestartFrom() allows to reuse already initialized structure.
+
+
+        INPUT PARAMETERS:
+            N       -   dimension, N>1
+                        * if given, only leading N elements of X are used
+                        * if not given, automatically determined from size of X
+            M       -   number of functions f[i]
+            X       -   initial solution, array[0..N-1]
+
+        OUTPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+
+        NOTES:
+        1. you may tune stopping conditions with MinLMSetCond() function
+        2. if target function contains exp() or other fast growing functions,  and
+           optimization algorithm makes too large steps which leads  to  overflow,
+           use MinLMSetStpMax() function to bound algorithm's steps.
+
+          -- ALGLIB --
+             Copyright 30.03.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minlmcreatevj(int n,
+            int m,
+            double[] x,
+            minlmstate state)
+        {
+            alglib.ap.assert(n>=1, "MinLMCreateVJ: N<1!");
+            alglib.ap.assert(m>=1, "MinLMCreateVJ: M<1!");
+            alglib.ap.assert(alglib.ap.len(x)>=n, "MinLMCreateVJ: Length(X)<N!");
+            alglib.ap.assert(apserv.isfinitevector(x, n), "MinLMCreateVJ: X contains infinite or NaN values!");
+            
+            //
+            // initialize, check parameters
+            //
+            state.n = n;
+            state.m = m;
+            state.algomode = 1;
+            state.hasf = false;
+            state.hasfi = true;
+            state.hasg = false;
+            
+            //
+            // second stage of initialization
+            //
+            lmprepare(n, m, false, state);
+            minlmsetacctype(state, 0);
+            minlmsetcond(state, 0, 0, 0, 0);
+            minlmsetxrep(state, false);
+            minlmsetstpmax(state, 0);
+            minlmrestartfrom(state, x);
+        }
+
+
+        /*************************************************************************
+                        IMPROVED LEVENBERG-MARQUARDT METHOD FOR
+                         NON-LINEAR LEAST SQUARES OPTIMIZATION
+
+        DESCRIPTION:
+        This function is used to find minimum of function which is represented  as
+        sum of squares:
+            F(x) = f[0]^2(x[0],...,x[n-1]) + ... + f[m-1]^2(x[0],...,x[n-1])
+        using value of function vector f[] only. Finite differences  are  used  to
+        calculate Jacobian.
+
+
+        REQUIREMENTS:
+        This algorithm will request following information during its operation:
+        * function vector f[] at given point X
+
+        There are several overloaded versions of  MinLMOptimize()  function  which
+        correspond  to  different LM-like optimization algorithms provided by this
+        unit. You should choose version which accepts fvec() callback.
+
+        You can try to initialize MinLMState structure with VJ  function and  then
+        use incorrect version  of  MinLMOptimize()  (for  example,  version  which
+        works with general form function and does not accept function vector), but
+        it will  lead  to  exception being thrown after first attempt to calculate
+        Jacobian.
+
+
+        USAGE:
+        1. User initializes algorithm state with MinLMCreateV() call
+        2. User tunes solver parameters with MinLMSetCond(),  MinLMSetStpMax() and
+           other functions
+        3. User calls MinLMOptimize() function which  takes algorithm  state   and
+           callback functions.
+        4. User calls MinLMResults() to get solution
+        5. Optionally, user may call MinLMRestartFrom() to solve  another  problem
+           with same N/M but another starting point and/or another function.
+           MinLMRestartFrom() allows to reuse already initialized structure.
+
+
+        INPUT PARAMETERS:
+            N       -   dimension, N>1
+                        * if given, only leading N elements of X are used
+                        * if not given, automatically determined from size of X
+            M       -   number of functions f[i]
+            X       -   initial solution, array[0..N-1]
+            DiffStep-   differentiation step, >0
+
+        OUTPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+
+        See also MinLMIteration, MinLMResults.
+
+        NOTES:
+        1. you may tune stopping conditions with MinLMSetCond() function
+        2. if target function contains exp() or other fast growing functions,  and
+           optimization algorithm makes too large steps which leads  to  overflow,
+           use MinLMSetStpMax() function to bound algorithm's steps.
+
+          -- ALGLIB --
+             Copyright 30.03.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minlmcreatev(int n,
+            int m,
+            double[] x,
+            double diffstep,
+            minlmstate state)
+        {
+            alglib.ap.assert(math.isfinite(diffstep), "MinLMCreateV: DiffStep is not finite!");
+            alglib.ap.assert((double)(diffstep)>(double)(0), "MinLMCreateV: DiffStep<=0!");
+            alglib.ap.assert(n>=1, "MinLMCreateV: N<1!");
+            alglib.ap.assert(m>=1, "MinLMCreateV: M<1!");
+            alglib.ap.assert(alglib.ap.len(x)>=n, "MinLMCreateV: Length(X)<N!");
+            alglib.ap.assert(apserv.isfinitevector(x, n), "MinLMCreateV: X contains infinite or NaN values!");
+            
+            //
+            // initialize
+            //
+            state.n = n;
+            state.m = m;
+            state.algomode = 0;
+            state.hasf = false;
+            state.hasfi = true;
+            state.hasg = false;
+            state.diffstep = diffstep;
+            
+            //
+            // second stage of initialization
+            //
+            lmprepare(n, m, false, state);
+            minlmsetacctype(state, 1);
+            minlmsetcond(state, 0, 0, 0, 0);
+            minlmsetxrep(state, false);
+            minlmsetstpmax(state, 0);
+            minlmrestartfrom(state, x);
+        }
+
+
+        /*************************************************************************
+            LEVENBERG-MARQUARDT-LIKE METHOD FOR NON-LINEAR OPTIMIZATION
+
+        DESCRIPTION:
+        This  function  is  used  to  find  minimum  of general form (not "sum-of-
+        -squares") function
+            F = F(x[0], ..., x[n-1])
+        using  its  gradient  and  Hessian.  Levenberg-Marquardt modification with
+        L-BFGS pre-optimization and internal pre-conditioned  L-BFGS  optimization
+        after each Levenberg-Marquardt step is used.
+
+
+        REQUIREMENTS:
+        This algorithm will request following information during its operation:
+
+        * function value F at given point X
+        * F and gradient G (simultaneously) at given point X
+        * F, G and Hessian H (simultaneously) at given point X
+
+        There are several overloaded versions of  MinLMOptimize()  function  which
+        correspond  to  different LM-like optimization algorithms provided by this
+        unit. You should choose version which accepts func(),  grad()  and  hess()
+        function pointers. First pointer is used to calculate F  at  given  point,
+        second  one  calculates  F(x)  and  grad F(x),  third one calculates F(x),
+        grad F(x), hess F(x).
+
+        You can try to initialize MinLMState structure with FGH-function and  then
+        use incorrect version of MinLMOptimize() (for example, version which  does
+        not provide Hessian matrix), but it will lead to  exception  being  thrown
+        after first attempt to calculate Hessian.
+
+
+        USAGE:
+        1. User initializes algorithm state with MinLMCreateFGH() call
+        2. User tunes solver parameters with MinLMSetCond(),  MinLMSetStpMax() and
+           other functions
+        3. User calls MinLMOptimize() function which  takes algorithm  state   and
+           pointers (delegates, etc.) to callback functions.
+        4. User calls MinLMResults() to get solution
+        5. Optionally, user may call MinLMRestartFrom() to solve  another  problem
+           with same N but another starting point and/or another function.
+           MinLMRestartFrom() allows to reuse already initialized structure.
+
+
+        INPUT PARAMETERS:
+            N       -   dimension, N>1
+                        * if given, only leading N elements of X are used
+                        * if not given, automatically determined from size of X
+            X       -   initial solution, array[0..N-1]
+
+        OUTPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+
+        NOTES:
+        1. you may tune stopping conditions with MinLMSetCond() function
+        2. if target function contains exp() or other fast growing functions,  and
+           optimization algorithm makes too large steps which leads  to  overflow,
+           use MinLMSetStpMax() function to bound algorithm's steps.
+
+          -- ALGLIB --
+             Copyright 30.03.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minlmcreatefgh(int n,
+            double[] x,
+            minlmstate state)
+        {
+            alglib.ap.assert(n>=1, "MinLMCreateFGH: N<1!");
+            alglib.ap.assert(alglib.ap.len(x)>=n, "MinLMCreateFGH: Length(X)<N!");
+            alglib.ap.assert(apserv.isfinitevector(x, n), "MinLMCreateFGH: X contains infinite or NaN values!");
+            
+            //
+            // initialize
+            //
+            state.n = n;
+            state.m = 0;
+            state.algomode = 2;
+            state.hasf = true;
+            state.hasfi = false;
+            state.hasg = true;
+            
+            //
+            // init2
+            //
+            lmprepare(n, 0, true, state);
+            minlmsetacctype(state, 2);
+            minlmsetcond(state, 0, 0, 0, 0);
+            minlmsetxrep(state, false);
+            minlmsetstpmax(state, 0);
+            minlmrestartfrom(state, x);
+        }
+
+
+        /*************************************************************************
+        This function sets stopping conditions for Levenberg-Marquardt optimization
+        algorithm.
+
+        INPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+            EpsG    -   >=0
+                        The  subroutine  finishes  its  work   if   the  condition
+                        |v|<EpsG is satisfied, where:
+                        * |.| means Euclidian norm
+                        * v - scaled gradient vector, v[i]=g[i]*s[i]
+                        * g - gradient
+                        * s - scaling coefficients set by MinLMSetScale()
+            EpsF    -   >=0
+                        The  subroutine  finishes  its work if on k+1-th iteration
+                        the  condition  |F(k+1)-F(k)|<=EpsF*max{|F(k)|,|F(k+1)|,1}
+                        is satisfied.
+            EpsX    -   >=0
+                        The subroutine finishes its work if  on  k+1-th  iteration
+                        the condition |v|<=EpsX is fulfilled, where:
+                        * |.| means Euclidian norm
+                        * v - scaled step vector, v[i]=dx[i]/s[i]
+                        * dx - ste pvector, dx=X(k+1)-X(k)
+                        * s - scaling coefficients set by MinLMSetScale()
+            MaxIts  -   maximum number of iterations. If MaxIts=0, the  number  of
+                        iterations   is    unlimited.   Only   Levenberg-Marquardt
+                        iterations  are  counted  (L-BFGS/CG  iterations  are  NOT
+                        counted because their cost is very low compared to that of
+                        LM).
+
+        Passing EpsG=0, EpsF=0, EpsX=0 and MaxIts=0 (simultaneously) will lead to
+        automatic stopping criterion selection (small EpsX).
+
+          -- ALGLIB --
+             Copyright 02.04.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minlmsetcond(minlmstate state,
+            double epsg,
+            double epsf,
+            double epsx,
+            int maxits)
+        {
+            alglib.ap.assert(math.isfinite(epsg), "MinLMSetCond: EpsG is not finite number!");
+            alglib.ap.assert((double)(epsg)>=(double)(0), "MinLMSetCond: negative EpsG!");
+            alglib.ap.assert(math.isfinite(epsf), "MinLMSetCond: EpsF is not finite number!");
+            alglib.ap.assert((double)(epsf)>=(double)(0), "MinLMSetCond: negative EpsF!");
+            alglib.ap.assert(math.isfinite(epsx), "MinLMSetCond: EpsX is not finite number!");
+            alglib.ap.assert((double)(epsx)>=(double)(0), "MinLMSetCond: negative EpsX!");
+            alglib.ap.assert(maxits>=0, "MinLMSetCond: negative MaxIts!");
+            if( (((double)(epsg)==(double)(0) && (double)(epsf)==(double)(0)) && (double)(epsx)==(double)(0)) && maxits==0 )
+            {
+                epsx = 1.0E-6;
+            }
+            state.epsg = epsg;
+            state.epsf = epsf;
+            state.epsx = epsx;
+            state.maxits = maxits;
+        }
+
+
+        /*************************************************************************
+        This function turns on/off reporting.
+
+        INPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+            NeedXRep-   whether iteration reports are needed or not
+
+        If NeedXRep is True, algorithm will call rep() callback function if  it is
+        provided to MinLMOptimize(). Both Levenberg-Marquardt and internal  L-BFGS
+        iterations are reported.
+
+          -- ALGLIB --
+             Copyright 02.04.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minlmsetxrep(minlmstate state,
+            bool needxrep)
+        {
+            state.xrep = needxrep;
+        }
+
+
+        /*************************************************************************
+        This function sets maximum step length
+
+        INPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+            StpMax  -   maximum step length, >=0. Set StpMax to 0.0,  if you don't
+                        want to limit step length.
+
+        Use this subroutine when you optimize target function which contains exp()
+        or  other  fast  growing  functions,  and optimization algorithm makes too
+        large  steps  which  leads  to overflow. This function allows us to reject
+        steps  that  are  too  large  (and  therefore  expose  us  to the possible
+        overflow) without actually calculating function value at the x+stp*d.
+
+        NOTE: non-zero StpMax leads to moderate  performance  degradation  because
+        intermediate  step  of  preconditioned L-BFGS optimization is incompatible
+        with limits on step size.
+
+          -- ALGLIB --
+             Copyright 02.04.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minlmsetstpmax(minlmstate state,
+            double stpmax)
+        {
+            alglib.ap.assert(math.isfinite(stpmax), "MinLMSetStpMax: StpMax is not finite!");
+            alglib.ap.assert((double)(stpmax)>=(double)(0), "MinLMSetStpMax: StpMax<0!");
+            state.stpmax = stpmax;
+        }
+
+
+        /*************************************************************************
+        This function sets scaling coefficients for LM optimizer.
+
+        ALGLIB optimizers use scaling matrices to test stopping  conditions  (step
+        size and gradient are scaled before comparison with tolerances).  Scale of
+        the I-th variable is a translation invariant measure of:
+        a) "how large" the variable is
+        b) how large the step should be to make significant changes in the function
+
+        Generally, scale is NOT considered to be a form of preconditioner.  But LM
+        optimizer is unique in that it uses scaling matrix both  in  the  stopping
+        condition tests and as Marquardt damping factor.
+
+        Proper scaling is very important for the algorithm performance. It is less
+        important for the quality of results, but still has some influence (it  is
+        easier  to  converge  when  variables  are  properly  scaled, so premature
+        stopping is possible when very badly scalled variables are  combined  with
+        relaxed stopping conditions).
+
+        INPUT PARAMETERS:
+            State   -   structure stores algorithm state
+            S       -   array[N], non-zero scaling coefficients
+                        S[i] may be negative, sign doesn't matter.
+
+          -- ALGLIB --
+             Copyright 14.01.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minlmsetscale(minlmstate state,
+            double[] s)
+        {
+            int i = 0;
+
+            alglib.ap.assert(alglib.ap.len(s)>=state.n, "MinLMSetScale: Length(S)<N");
+            for(i=0; i<=state.n-1; i++)
+            {
+                alglib.ap.assert(math.isfinite(s[i]), "MinLMSetScale: S contains infinite or NAN elements");
+                alglib.ap.assert((double)(s[i])!=(double)(0), "MinLMSetScale: S contains zero elements");
+                state.s[i] = Math.Abs(s[i]);
+            }
+        }
+
+
+        /*************************************************************************
+        This function sets boundary constraints for LM optimizer
+
+        Boundary constraints are inactive by default (after initial creation).
+        They are preserved until explicitly turned off with another SetBC() call.
+
+        INPUT PARAMETERS:
+            State   -   structure stores algorithm state
+            BndL    -   lower bounds, array[N].
+                        If some (all) variables are unbounded, you may specify
+                        very small number or -INF (latter is recommended because
+                        it will allow solver to use better algorithm).
+            BndU    -   upper bounds, array[N].
+                        If some (all) variables are unbounded, you may specify
+                        very large number or +INF (latter is recommended because
+                        it will allow solver to use better algorithm).
+
+        NOTE 1: it is possible to specify BndL[i]=BndU[i]. In this case I-th
+        variable will be "frozen" at X[i]=BndL[i]=BndU[i].
+
+        NOTE 2: this solver has following useful properties:
+        * bound constraints are always satisfied exactly
+        * function is evaluated only INSIDE area specified by bound constraints
+          or at its boundary
+
+          -- ALGLIB --
+             Copyright 14.01.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minlmsetbc(minlmstate state,
+            double[] bndl,
+            double[] bndu)
+        {
+            int i = 0;
+            int n = 0;
+
+            n = state.n;
+            alglib.ap.assert(alglib.ap.len(bndl)>=n, "MinLMSetBC: Length(BndL)<N");
+            alglib.ap.assert(alglib.ap.len(bndu)>=n, "MinLMSetBC: Length(BndU)<N");
+            for(i=0; i<=n-1; i++)
+            {
+                alglib.ap.assert(math.isfinite(bndl[i]) || Double.IsNegativeInfinity(bndl[i]), "MinLMSetBC: BndL contains NAN or +INF");
+                alglib.ap.assert(math.isfinite(bndu[i]) || Double.IsPositiveInfinity(bndu[i]), "MinLMSetBC: BndU contains NAN or -INF");
+                state.bndl[i] = bndl[i];
+                state.havebndl[i] = math.isfinite(bndl[i]);
+                state.bndu[i] = bndu[i];
+                state.havebndu[i] = math.isfinite(bndu[i]);
+            }
+        }
+
+
+        /*************************************************************************
+        This function is used to change acceleration settings
+
+        You can choose between three acceleration strategies:
+        * AccType=0, no acceleration.
+        * AccType=1, secant updates are used to update quadratic model after  each
+          iteration. After fixed number of iterations (or after  model  breakdown)
+          we  recalculate  quadratic  model  using  analytic  Jacobian  or  finite
+          differences. Number of secant-based iterations depends  on  optimization
+          settings: about 3 iterations - when we have analytic Jacobian, up to 2*N
+          iterations - when we use finite differences to calculate Jacobian.
+
+        AccType=1 is recommended when Jacobian  calculation  cost  is  prohibitive
+        high (several Mx1 function vector calculations  followed  by  several  NxN
+        Cholesky factorizations are faster than calculation of one M*N  Jacobian).
+        It should also be used when we have no Jacobian, because finite difference
+        approximation takes too much time to compute.
+
+        Table below list  optimization  protocols  (XYZ  protocol  corresponds  to
+        MinLMCreateXYZ) and acceleration types they support (and use by  default).
+
+        ACCELERATION TYPES SUPPORTED BY OPTIMIZATION PROTOCOLS:
+
+        protocol    0   1   comment
+        V           +   +
+        VJ          +   +
+        FGH         +
+
+        DAFAULT VALUES:
+
+        protocol    0   1   comment
+        V               x   without acceleration it is so slooooooooow
+        VJ          x
+        FGH         x
+
+        NOTE: this  function should be called before optimization. Attempt to call
+        it during algorithm iterations may result in unexpected behavior.
+
+        NOTE: attempt to call this function with unsupported protocol/acceleration
+        combination will result in exception being thrown.
+
+          -- ALGLIB --
+             Copyright 14.10.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minlmsetacctype(minlmstate state,
+            int acctype)
+        {
+            alglib.ap.assert((acctype==0 || acctype==1) || acctype==2, "MinLMSetAccType: incorrect AccType!");
+            if( acctype==2 )
+            {
+                acctype = 0;
+            }
+            if( acctype==0 )
+            {
+                state.maxmodelage = 0;
+                state.makeadditers = false;
+                return;
+            }
+            if( acctype==1 )
+            {
+                alglib.ap.assert(state.hasfi, "MinLMSetAccType: AccType=1 is incompatible with current protocol!");
+                if( state.algomode==0 )
+                {
+                    state.maxmodelage = 2*state.n;
+                }
+                else
+                {
+                    state.maxmodelage = smallmodelage;
+                }
+                state.makeadditers = false;
+                return;
+            }
+        }
+
+
+        /*************************************************************************
+        NOTES:
+
+        1. Depending on function used to create state  structure,  this  algorithm
+           may accept Jacobian and/or Hessian and/or gradient.  According  to  the
+           said above, there ase several versions of this function,  which  accept
+           different sets of callbacks.
+
+           This flexibility opens way to subtle errors - you may create state with
+           MinLMCreateFGH() (optimization using Hessian), but call function  which
+           does not accept Hessian. So when algorithm will request Hessian,  there
+           will be no callback to call. In this case exception will be thrown.
+
+           Be careful to avoid such errors because there is no way to find them at
+           compile time - you can see them at runtime only.
+
+          -- ALGLIB --
+             Copyright 10.03.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static bool minlmiteration(minlmstate state)
+        {
+            bool result = new bool();
+            int n = 0;
+            int m = 0;
+            bool bflag = new bool();
+            int iflag = 0;
+            double v = 0;
+            double s = 0;
+            double t = 0;
+            int i = 0;
+            int k = 0;
+            int i_ = 0;
+
+            
+            //
+            // Reverse communication preparations
+            // I know it looks ugly, but it works the same way
+            // anywhere from C++ to Python.
+            //
+            // This code initializes locals by:
+            // * random values determined during code
+            //   generation - on first subroutine call
+            // * values from previous call - on subsequent calls
+            //
+            if( state.rstate.stage>=0 )
+            {
+                n = state.rstate.ia[0];
+                m = state.rstate.ia[1];
+                iflag = state.rstate.ia[2];
+                i = state.rstate.ia[3];
+                k = state.rstate.ia[4];
+                bflag = state.rstate.ba[0];
+                v = state.rstate.ra[0];
+                s = state.rstate.ra[1];
+                t = state.rstate.ra[2];
+            }
+            else
+            {
+                n = -983;
+                m = -989;
+                iflag = -834;
+                i = 900;
+                k = -287;
+                bflag = false;
+                v = 214;
+                s = -338;
+                t = -686;
+            }
+            if( state.rstate.stage==0 )
+            {
+                goto lbl_0;
+            }
+            if( state.rstate.stage==1 )
+            {
+                goto lbl_1;
+            }
+            if( state.rstate.stage==2 )
+            {
+                goto lbl_2;
+            }
+            if( state.rstate.stage==3 )
+            {
+                goto lbl_3;
+            }
+            if( state.rstate.stage==4 )
+            {
+                goto lbl_4;
+            }
+            if( state.rstate.stage==5 )
+            {
+                goto lbl_5;
+            }
+            if( state.rstate.stage==6 )
+            {
+                goto lbl_6;
+            }
+            if( state.rstate.stage==7 )
+            {
+                goto lbl_7;
+            }
+            if( state.rstate.stage==8 )
+            {
+                goto lbl_8;
+            }
+            if( state.rstate.stage==9 )
+            {
+                goto lbl_9;
+            }
+            if( state.rstate.stage==10 )
+            {
+                goto lbl_10;
+            }
+            if( state.rstate.stage==11 )
+            {
+                goto lbl_11;
+            }
+            if( state.rstate.stage==12 )
+            {
+                goto lbl_12;
+            }
+            if( state.rstate.stage==13 )
+            {
+                goto lbl_13;
+            }
+            if( state.rstate.stage==14 )
+            {
+                goto lbl_14;
+            }
+            if( state.rstate.stage==15 )
+            {
+                goto lbl_15;
+            }
+            
+            //
+            // Routine body
+            //
+            
+            //
+            // prepare
+            //
+            n = state.n;
+            m = state.m;
+            state.repiterationscount = 0;
+            state.repterminationtype = 0;
+            state.repnfunc = 0;
+            state.repnjac = 0;
+            state.repngrad = 0;
+            state.repnhess = 0;
+            state.repncholesky = 0;
+            
+            //
+            // check consistency of constraints,
+            // enforce feasibility of the solution
+            // set constraints
+            //
+            if( !optserv.enforceboundaryconstraints(ref state.xbase, state.bndl, state.havebndl, state.bndu, state.havebndu, n, 0) )
+            {
+                state.repterminationtype = -3;
+                result = false;
+                return result;
+            }
+            minqp.minqpsetbc(state.qpstate, state.bndl, state.bndu);
+            
+            //
+            // Initial report of current point
+            //
+            // Note 1: we rewrite State.X twice because
+            // user may accidentally change it after first call.
+            //
+            // Note 2: we set NeedF or NeedFI depending on what
+            // information about function we have.
+            //
+            if( !state.xrep )
+            {
+                goto lbl_16;
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.xbase[i_];
+            }
+            clearrequestfields(state);
+            if( !state.hasf )
+            {
+                goto lbl_18;
+            }
+            state.needf = true;
+            state.rstate.stage = 0;
+            goto lbl_rcomm;
+        lbl_0:
+            state.needf = false;
+            goto lbl_19;
+        lbl_18:
+            alglib.ap.assert(state.hasfi, "MinLM: internal error 2!");
+            state.needfi = true;
+            state.rstate.stage = 1;
+            goto lbl_rcomm;
+        lbl_1:
+            state.needfi = false;
+            v = 0.0;
+            for(i_=0; i_<=m-1;i_++)
+            {
+                v += state.fi[i_]*state.fi[i_];
+            }
+            state.f = v;
+        lbl_19:
+            state.repnfunc = state.repnfunc+1;
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.xbase[i_];
+            }
+            clearrequestfields(state);
+            state.xupdated = true;
+            state.rstate.stage = 2;
+            goto lbl_rcomm;
+        lbl_2:
+            state.xupdated = false;
+        lbl_16:
+            
+            //
+            // Prepare control variables
+            //
+            state.nu = 1;
+            state.lambdav = -math.maxrealnumber;
+            state.modelage = state.maxmodelage+1;
+            state.deltaxready = false;
+            state.deltafready = false;
+            
+            //
+            // Main cycle.
+            //
+            // We move through it until either:
+            // * one of the stopping conditions is met
+            // * we decide that stopping conditions are too stringent
+            //   and break from cycle
+            //
+            //
+        lbl_20:
+            if( false )
+            {
+                goto lbl_21;
+            }
+            
+            //
+            // First, we have to prepare quadratic model for our function.
+            // We use BFlag to ensure that model is prepared;
+            // if it is false at the end of this block, something went wrong.
+            //
+            // We may either calculate brand new model or update old one.
+            //
+            // Before this block we have:
+            // * State.XBase            - current position.
+            // * State.DeltaX           - if DeltaXReady is True
+            // * State.DeltaF           - if DeltaFReady is True
+            //
+            // After this block is over, we will have:
+            // * State.XBase            - base point (unchanged)
+            // * State.FBase            - F(XBase)
+            // * State.GBase            - linear term
+            // * State.QuadraticModel   - quadratic term
+            // * State.LambdaV          - current estimate for lambda
+            //
+            // We also clear DeltaXReady/DeltaFReady flags
+            // after initialization is done.
+            //
+            bflag = false;
+            if( !(state.algomode==0 || state.algomode==1) )
+            {
+                goto lbl_22;
+            }
+            
+            //
+            // Calculate f[] and Jacobian
+            //
+            if( !(state.modelage>state.maxmodelage || !(state.deltaxready && state.deltafready)) )
+            {
+                goto lbl_24;
+            }
+            
+            //
+            // Refresh model (using either finite differences or analytic Jacobian)
+            //
+            if( state.algomode!=0 )
+            {
+                goto lbl_26;
+            }
+            
+            //
+            // Optimization using F values only.
+            // Use finite differences to estimate Jacobian.
+            //
+            alglib.ap.assert(state.hasfi, "MinLMIteration: internal error when estimating Jacobian (no f[])");
+            k = 0;
+        lbl_28:
+            if( k>n-1 )
+            {
+                goto lbl_30;
+            }
+            
+            //
+            // We guard X[k] from leaving [BndL,BndU].
+            // In case BndL=BndU, we assume that derivative in this direction is zero.
+            //
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.xbase[i_];
+            }
+            state.x[k] = state.x[k]-state.s[k]*state.diffstep;
+            if( state.havebndl[k] )
+            {
+                state.x[k] = Math.Max(state.x[k], state.bndl[k]);
+            }
+            if( state.havebndu[k] )
+            {
+                state.x[k] = Math.Min(state.x[k], state.bndu[k]);
+            }
+            state.xm1 = state.x[k];
+            clearrequestfields(state);
+            state.needfi = true;
+            state.rstate.stage = 3;
+            goto lbl_rcomm;
+        lbl_3:
+            state.repnfunc = state.repnfunc+1;
+            for(i_=0; i_<=m-1;i_++)
+            {
+                state.fm1[i_] = state.fi[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.xbase[i_];
+            }
+            state.x[k] = state.x[k]+state.s[k]*state.diffstep;
+            if( state.havebndl[k] )
+            {
+                state.x[k] = Math.Max(state.x[k], state.bndl[k]);
+            }
+            if( state.havebndu[k] )
+            {
+                state.x[k] = Math.Min(state.x[k], state.bndu[k]);
+            }
+            state.xp1 = state.x[k];
+            clearrequestfields(state);
+            state.needfi = true;
+            state.rstate.stage = 4;
+            goto lbl_rcomm;
+        lbl_4:
+            state.repnfunc = state.repnfunc+1;
+            for(i_=0; i_<=m-1;i_++)
+            {
+                state.fp1[i_] = state.fi[i_];
+            }
+            v = state.xp1-state.xm1;
+            if( (double)(v)!=(double)(0) )
+            {
+                v = 1/v;
+                for(i_=0; i_<=m-1;i_++)
+                {
+                    state.j[i_,k] = v*state.fp1[i_];
+                }
+                for(i_=0; i_<=m-1;i_++)
+                {
+                    state.j[i_,k] = state.j[i_,k] - v*state.fm1[i_];
+                }
+            }
+            else
+            {
+                for(i=0; i<=m-1; i++)
+                {
+                    state.j[i,k] = 0;
+                }
+            }
+            k = k+1;
+            goto lbl_28;
+        lbl_30:
+            
+            //
+            // Calculate F(XBase)
+            //
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.xbase[i_];
+            }
+            clearrequestfields(state);
+            state.needfi = true;
+            state.rstate.stage = 5;
+            goto lbl_rcomm;
+        lbl_5:
+            state.needfi = false;
+            state.repnfunc = state.repnfunc+1;
+            state.repnjac = state.repnjac+1;
+            
+            //
+            // New model
+            //
+            state.modelage = 0;
+            goto lbl_27;
+        lbl_26:
+            
+            //
+            // Obtain f[] and Jacobian
+            //
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.xbase[i_];
+            }
+            clearrequestfields(state);
+            state.needfij = true;
+            state.rstate.stage = 6;
+            goto lbl_rcomm;
+        lbl_6:
+            state.needfij = false;
+            state.repnfunc = state.repnfunc+1;
+            state.repnjac = state.repnjac+1;
+            
+            //
+            // New model
+            //
+            state.modelage = 0;
+        lbl_27:
+            goto lbl_25;
+        lbl_24:
+            
+            //
+            // State.J contains Jacobian or its current approximation;
+            // refresh it using secant updates:
+            //
+            // f(x0+dx) = f(x0) + J*dx,
+            // J_new = J_old + u*h'
+            // h = x_new-x_old
+            // u = (f_new - f_old - J_old*h)/(h'h)
+            //
+            // We can explicitly generate h and u, but it is
+            // preferential to do in-place calculations. Only
+            // I-th row of J_old is needed to calculate u[I],
+            // so we can update J row by row in one pass.
+            //
+            // NOTE: we expect that State.XBase contains new point,
+            // State.FBase contains old point, State.DeltaX and
+            // State.DeltaY contain updates from last step.
+            //
+            alglib.ap.assert(state.deltaxready && state.deltafready, "MinLMIteration: uninitialized DeltaX/DeltaF");
+            t = 0.0;
+            for(i_=0; i_<=n-1;i_++)
+            {
+                t += state.deltax[i_]*state.deltax[i_];
+            }
+            alglib.ap.assert((double)(t)!=(double)(0), "MinLM: internal error (T=0)");
+            for(i=0; i<=m-1; i++)
+            {
+                v = 0.0;
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    v += state.j[i,i_]*state.deltax[i_];
+                }
+                v = (state.deltaf[i]-v)/t;
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    state.j[i,i_] = state.j[i,i_] + v*state.deltax[i_];
+                }
+            }
+            for(i_=0; i_<=m-1;i_++)
+            {
+                state.fi[i_] = state.fibase[i_];
+            }
+            for(i_=0; i_<=m-1;i_++)
+            {
+                state.fi[i_] = state.fi[i_] + state.deltaf[i_];
+            }
+            
+            //
+            // Increase model age
+            //
+            state.modelage = state.modelage+1;
+        lbl_25:
+            
+            //
+            // Generate quadratic model:
+            //     f(xbase+dx) =
+            //       = (f0 + J*dx)'(f0 + J*dx)
+            //       = f0^2 + dx'J'f0 + f0*J*dx + dx'J'J*dx
+            //       = f0^2 + 2*f0*J*dx + dx'J'J*dx
+            //
+            // Note that we calculate 2*(J'J) instead of J'J because
+            // our quadratic model is based on Tailor decomposition,
+            // i.e. it has 0.5 before quadratic term.
+            //
+            ablas.rmatrixgemm(n, n, m, 2.0, state.j, 0, 0, 1, state.j, 0, 0, 0, 0.0, ref state.quadraticmodel, 0, 0);
+            ablas.rmatrixmv(n, m, state.j, 0, 0, 1, state.fi, 0, ref state.gbase, 0);
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.gbase[i_] = 2*state.gbase[i_];
+            }
+            v = 0.0;
+            for(i_=0; i_<=m-1;i_++)
+            {
+                v += state.fi[i_]*state.fi[i_];
+            }
+            state.fbase = v;
+            for(i_=0; i_<=m-1;i_++)
+            {
+                state.fibase[i_] = state.fi[i_];
+            }
+            
+            //
+            // set control variables
+            //
+            bflag = true;
+        lbl_22:
+            if( state.algomode!=2 )
+            {
+                goto lbl_31;
+            }
+            alglib.ap.assert(!state.hasfi, "MinLMIteration: internal error (HasFI is True in Hessian-based mode)");
+            
+            //
+            // Obtain F, G, H
+            //
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.xbase[i_];
+            }
+            clearrequestfields(state);
+            state.needfgh = true;
+            state.rstate.stage = 7;
+            goto lbl_rcomm;
+        lbl_7:
+            state.needfgh = false;
+            state.repnfunc = state.repnfunc+1;
+            state.repngrad = state.repngrad+1;
+            state.repnhess = state.repnhess+1;
+            ablas.rmatrixcopy(n, n, state.h, 0, 0, ref state.quadraticmodel, 0, 0);
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.gbase[i_] = state.g[i_];
+            }
+            state.fbase = state.f;
+            
+            //
+            // set control variables
+            //
+            bflag = true;
+            state.modelage = 0;
+        lbl_31:
+            alglib.ap.assert(bflag, "MinLM: internal integrity check failed!");
+            state.deltaxready = false;
+            state.deltafready = false;
+            
+            //
+            // If Lambda is not initialized, initialize it using quadratic model
+            //
+            if( (double)(state.lambdav)<(double)(0) )
+            {
+                state.lambdav = 0;
+                for(i=0; i<=n-1; i++)
+                {
+                    state.lambdav = Math.Max(state.lambdav, Math.Abs(state.quadraticmodel[i,i])*math.sqr(state.s[i]));
+                }
+                state.lambdav = 0.001*state.lambdav;
+                if( (double)(state.lambdav)==(double)(0) )
+                {
+                    state.lambdav = 1;
+                }
+            }
+            
+            //
+            // Test stopping conditions for function gradient
+            //
+            if( (double)(boundedscaledantigradnorm(state, state.xbase, state.gbase))>(double)(state.epsg) )
+            {
+                goto lbl_33;
+            }
+            if( state.modelage!=0 )
+            {
+                goto lbl_35;
+            }
+            
+            //
+            // Model is fresh, we can rely on it and terminate algorithm
+            //
+            state.repterminationtype = 4;
+            if( !state.xrep )
+            {
+                goto lbl_37;
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.xbase[i_];
+            }
+            state.f = state.fbase;
+            clearrequestfields(state);
+            state.xupdated = true;
+            state.rstate.stage = 8;
+            goto lbl_rcomm;
+        lbl_8:
+            state.xupdated = false;
+        lbl_37:
+            result = false;
+            return result;
+            goto lbl_36;
+        lbl_35:
+            
+            //
+            // Model is not fresh, we should refresh it and test
+            // conditions once more
+            //
+            state.modelage = state.maxmodelage+1;
+            goto lbl_20;
+        lbl_36:
+        lbl_33:
+            
+            //
+            // Find value of Levenberg-Marquardt damping parameter which:
+            // * leads to positive definite damped model
+            // * within bounds specified by StpMax
+            // * generates step which decreases function value
+            //
+            // After this block IFlag is set to:
+            // * -3, if constraints are infeasible
+            // * -2, if model update is needed (either Lambda growth is too large
+            //       or step is too short, but we can't rely on model and stop iterations)
+            // * -1, if model is fresh, Lambda have grown too large, termination is needed
+            // *  0, if everything is OK, continue iterations
+            //
+            // State.Nu can have any value on enter, but after exit it is set to 1.0
+            //
+            iflag = -99;
+        lbl_39:
+            if( false )
+            {
+                goto lbl_40;
+            }
+            
+            //
+            // Do we need model update?
+            //
+            if( state.modelage>0 && (double)(state.nu)>=(double)(suspiciousnu) )
+            {
+                iflag = -2;
+                goto lbl_40;
+            }
+            
+            //
+            // Setup quadratic solver and solve quadratic programming problem.
+            // After problem is solved we'll try to bound step by StpMax
+            // (Lambda will be increased if step size is too large).
+            //
+            // We use BFlag variable to indicate that we have to increase Lambda.
+            // If it is False, we will try to increase Lambda and move to new iteration.
+            //
+            bflag = true;
+            minqp.minqpsetstartingpointfast(state.qpstate, state.xbase);
+            minqp.minqpsetoriginfast(state.qpstate, state.xbase);
+            minqp.minqpsetlineartermfast(state.qpstate, state.gbase);
+            minqp.minqpsetquadratictermfast(state.qpstate, state.quadraticmodel, true, 0.0);
+            for(i=0; i<=n-1; i++)
+            {
+                state.tmp0[i] = state.quadraticmodel[i,i]+state.lambdav/math.sqr(state.s[i]);
+            }
+            minqp.minqprewritediagonal(state.qpstate, state.tmp0);
+            minqp.minqpoptimize(state.qpstate);
+            minqp.minqpresultsbuf(state.qpstate, ref state.xdir, state.qprep);
+            if( state.qprep.terminationtype>0 )
+            {
+                
+                //
+                // successful solution of QP problem
+                //
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    state.xdir[i_] = state.xdir[i_] - state.xbase[i_];
+                }
+                v = 0.0;
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    v += state.xdir[i_]*state.xdir[i_];
+                }
+                if( math.isfinite(v) )
+                {
+                    v = Math.Sqrt(v);
+                    if( (double)(state.stpmax)>(double)(0) && (double)(v)>(double)(state.stpmax) )
+                    {
+                        bflag = false;
+                    }
+                }
+                else
+                {
+                    bflag = false;
+                }
+            }
+            else
+            {
+                
+                //
+                // Either problem is non-convex (increase LambdaV) or constraints are inconsistent
+                //
+                alglib.ap.assert(state.qprep.terminationtype==-3 || state.qprep.terminationtype==-5, "MinLM: unexpected completion code from QP solver");
+                if( state.qprep.terminationtype==-3 )
+                {
+                    iflag = -3;
+                    goto lbl_40;
+                }
+                bflag = false;
+            }
+            if( !bflag )
+            {
+                
+                //
+                // Solution failed:
+                // try to increase lambda to make matrix positive definite and continue.
+                //
+                if( !increaselambda(ref state.lambdav, ref state.nu) )
+                {
+                    iflag = -1;
+                    goto lbl_40;
+                }
+                goto lbl_39;
+            }
+            
+            //
+            // Step in State.XDir and it is bounded by StpMax.
+            //
+            // We should check stopping conditions on step size here.
+            // DeltaX, which is used for secant updates, is initialized here.
+            //
+            // This code is a bit tricky because sometimes XDir<>0, but
+            // it is so small that XDir+XBase==XBase (in finite precision
+            // arithmetics). So we set DeltaX to XBase, then
+            // add XDir, and then subtract XBase to get exact value of
+            // DeltaX.
+            //
+            // Step length is estimated using DeltaX.
+            //
+            // NOTE: stopping conditions are tested
+            // for fresh models only (ModelAge=0)
+            //
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.deltax[i_] = state.xbase[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.deltax[i_] = state.deltax[i_] + state.xdir[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.deltax[i_] = state.deltax[i_] - state.xbase[i_];
+            }
+            state.deltaxready = true;
+            v = 0.0;
+            for(i=0; i<=n-1; i++)
+            {
+                v = v+math.sqr(state.deltax[i]/state.s[i]);
+            }
+            v = Math.Sqrt(v);
+            if( (double)(v)>(double)(state.epsx) )
+            {
+                goto lbl_41;
+            }
+            if( state.modelage!=0 )
+            {
+                goto lbl_43;
+            }
+            
+            //
+            // Step is too short, model is fresh and we can rely on it.
+            // Terminating.
+            //
+            state.repterminationtype = 2;
+            if( !state.xrep )
+            {
+                goto lbl_45;
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.xbase[i_];
+            }
+            state.f = state.fbase;
+            clearrequestfields(state);
+            state.xupdated = true;
+            state.rstate.stage = 9;
+            goto lbl_rcomm;
+        lbl_9:
+            state.xupdated = false;
+        lbl_45:
+            result = false;
+            return result;
+            goto lbl_44;
+        lbl_43:
+            
+            //
+            // Step is suspiciously short, but model is not fresh
+            // and we can't rely on it.
+            //
+            iflag = -2;
+            goto lbl_40;
+        lbl_44:
+        lbl_41:
+            
+            //
+            // Let's evaluate new step:
+            // a) if we have Fi vector, we evaluate it using rcomm, and
+            //    then we manually calculate State.F as sum of squares of Fi[]
+            // b) if we have F value, we just evaluate it through rcomm interface
+            //
+            // We prefer (a) because we may need Fi vector for additional
+            // iterations
+            //
+            alglib.ap.assert(state.hasfi || state.hasf, "MinLM: internal error 2!");
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.xbase[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.x[i_] + state.xdir[i_];
+            }
+            clearrequestfields(state);
+            if( !state.hasfi )
+            {
+                goto lbl_47;
+            }
+            state.needfi = true;
+            state.rstate.stage = 10;
+            goto lbl_rcomm;
+        lbl_10:
+            state.needfi = false;
+            v = 0.0;
+            for(i_=0; i_<=m-1;i_++)
+            {
+                v += state.fi[i_]*state.fi[i_];
+            }
+            state.f = v;
+            for(i_=0; i_<=m-1;i_++)
+            {
+                state.deltaf[i_] = state.fi[i_];
+            }
+            for(i_=0; i_<=m-1;i_++)
+            {
+                state.deltaf[i_] = state.deltaf[i_] - state.fibase[i_];
+            }
+            state.deltafready = true;
+            goto lbl_48;
+        lbl_47:
+            state.needf = true;
+            state.rstate.stage = 11;
+            goto lbl_rcomm;
+        lbl_11:
+            state.needf = false;
+        lbl_48:
+            state.repnfunc = state.repnfunc+1;
+            if( (double)(state.f)>=(double)(state.fbase) )
+            {
+                
+                //
+                // Increase lambda and continue
+                //
+                if( !increaselambda(ref state.lambdav, ref state.nu) )
+                {
+                    iflag = -1;
+                    goto lbl_40;
+                }
+                goto lbl_39;
+            }
+            
+            //
+            // We've found our step!
+            //
+            iflag = 0;
+            goto lbl_40;
+            goto lbl_39;
+        lbl_40:
+            state.nu = 1;
+            alglib.ap.assert(iflag>=-3 && iflag<=0, "MinLM: internal integrity check failed!");
+            if( iflag==-3 )
+            {
+                state.repterminationtype = -3;
+                result = false;
+                return result;
+            }
+            if( iflag==-2 )
+            {
+                state.modelage = state.maxmodelage+1;
+                goto lbl_20;
+            }
+            if( iflag==-1 )
+            {
+                goto lbl_21;
+            }
+            
+            //
+            // Levenberg-Marquardt step is ready.
+            // Compare predicted vs. actual decrease and decide what to do with lambda.
+            //
+            // NOTE: we expect that State.DeltaX contains direction of step,
+            // State.F contains function value at new point.
+            //
+            alglib.ap.assert(state.deltaxready, "MinLM: deltaX is not ready");
+            t = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                v = 0.0;
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    v += state.quadraticmodel[i,i_]*state.deltax[i_];
+                }
+                t = t+state.deltax[i]*state.gbase[i]+0.5*state.deltax[i]*v;
+            }
+            state.predicteddecrease = -t;
+            state.actualdecrease = -(state.f-state.fbase);
+            if( (double)(state.predicteddecrease)<=(double)(0) )
+            {
+                goto lbl_21;
+            }
+            v = state.actualdecrease/state.predicteddecrease;
+            if( (double)(v)>=(double)(0.1) )
+            {
+                goto lbl_49;
+            }
+            if( increaselambda(ref state.lambdav, ref state.nu) )
+            {
+                goto lbl_51;
+            }
+            
+            //
+            // Lambda is too large, we have to break iterations.
+            //
+            state.repterminationtype = 7;
+            if( !state.xrep )
+            {
+                goto lbl_53;
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.xbase[i_];
+            }
+            state.f = state.fbase;
+            clearrequestfields(state);
+            state.xupdated = true;
+            state.rstate.stage = 12;
+            goto lbl_rcomm;
+        lbl_12:
+            state.xupdated = false;
+        lbl_53:
+            result = false;
+            return result;
+        lbl_51:
+        lbl_49:
+            if( (double)(v)>(double)(0.5) )
+            {
+                decreaselambda(ref state.lambdav, ref state.nu);
+            }
+            
+            //
+            // Accept step, report it and
+            // test stopping conditions on iterations count and function decrease.
+            //
+            // NOTE: we expect that State.DeltaX contains direction of step,
+            // State.F contains function value at new point.
+            //
+            // NOTE2: we should update XBase ONLY. In the beginning of the next
+            // iteration we expect that State.FIBase is NOT updated and
+            // contains old value of a function vector.
+            //
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.xbase[i_] = state.xbase[i_] + state.deltax[i_];
+            }
+            if( !state.xrep )
+            {
+                goto lbl_55;
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.xbase[i_];
+            }
+            clearrequestfields(state);
+            state.xupdated = true;
+            state.rstate.stage = 13;
+            goto lbl_rcomm;
+        lbl_13:
+            state.xupdated = false;
+        lbl_55:
+            state.repiterationscount = state.repiterationscount+1;
+            if( state.repiterationscount>=state.maxits && state.maxits>0 )
+            {
+                state.repterminationtype = 5;
+            }
+            if( state.modelage==0 )
+            {
+                if( (double)(Math.Abs(state.f-state.fbase))<=(double)(state.epsf*Math.Max(1, Math.Max(Math.Abs(state.f), Math.Abs(state.fbase)))) )
+                {
+                    state.repterminationtype = 1;
+                }
+            }
+            if( state.repterminationtype<=0 )
+            {
+                goto lbl_57;
+            }
+            if( !state.xrep )
+            {
+                goto lbl_59;
+            }
+            
+            //
+            // Report: XBase contains new point, F contains function value at new point
+            //
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.xbase[i_];
+            }
+            clearrequestfields(state);
+            state.xupdated = true;
+            state.rstate.stage = 14;
+            goto lbl_rcomm;
+        lbl_14:
+            state.xupdated = false;
+        lbl_59:
+            result = false;
+            return result;
+        lbl_57:
+            state.modelage = state.modelage+1;
+            goto lbl_20;
+        lbl_21:
+            
+            //
+            // Lambda is too large, we have to break iterations.
+            //
+            state.repterminationtype = 7;
+            if( !state.xrep )
+            {
+                goto lbl_61;
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.xbase[i_];
+            }
+            state.f = state.fbase;
+            clearrequestfields(state);
+            state.xupdated = true;
+            state.rstate.stage = 15;
+            goto lbl_rcomm;
+        lbl_15:
+            state.xupdated = false;
+        lbl_61:
+            result = false;
+            return result;
+            
+            //
+            // Saving state
+            //
+        lbl_rcomm:
+            result = true;
+            state.rstate.ia[0] = n;
+            state.rstate.ia[1] = m;
+            state.rstate.ia[2] = iflag;
+            state.rstate.ia[3] = i;
+            state.rstate.ia[4] = k;
+            state.rstate.ba[0] = bflag;
+            state.rstate.ra[0] = v;
+            state.rstate.ra[1] = s;
+            state.rstate.ra[2] = t;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Levenberg-Marquardt algorithm results
+
+        INPUT PARAMETERS:
+            State   -   algorithm state
+
+        OUTPUT PARAMETERS:
+            X       -   array[0..N-1], solution
+            Rep     -   optimization report;
+                        see comments for this structure for more info.
+
+          -- ALGLIB --
+             Copyright 10.03.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minlmresults(minlmstate state,
+            ref double[] x,
+            minlmreport rep)
+        {
+            x = new double[0];
+
+            minlmresultsbuf(state, ref x, rep);
+        }
+
+
+        /*************************************************************************
+        Levenberg-Marquardt algorithm results
+
+        Buffered implementation of MinLMResults(), which uses pre-allocated buffer
+        to store X[]. If buffer size is  too  small,  it  resizes  buffer.  It  is
+        intended to be used in the inner cycles of performance critical algorithms
+        where array reallocation penalty is too large to be ignored.
+
+          -- ALGLIB --
+             Copyright 10.03.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minlmresultsbuf(minlmstate state,
+            ref double[] x,
+            minlmreport rep)
+        {
+            int i_ = 0;
+
+            if( alglib.ap.len(x)<state.n )
+            {
+                x = new double[state.n];
+            }
+            for(i_=0; i_<=state.n-1;i_++)
+            {
+                x[i_] = state.x[i_];
+            }
+            rep.iterationscount = state.repiterationscount;
+            rep.terminationtype = state.repterminationtype;
+            rep.nfunc = state.repnfunc;
+            rep.njac = state.repnjac;
+            rep.ngrad = state.repngrad;
+            rep.nhess = state.repnhess;
+            rep.ncholesky = state.repncholesky;
+        }
+
+
+        /*************************************************************************
+        This  subroutine  restarts  LM  algorithm from new point. All optimization
+        parameters are left unchanged.
+
+        This  function  allows  to  solve multiple  optimization  problems  (which
+        must have same number of dimensions) without object reallocation penalty.
+
+        INPUT PARAMETERS:
+            State   -   structure used for reverse communication previously
+                        allocated with MinLMCreateXXX call.
+            X       -   new starting point.
+
+          -- ALGLIB --
+             Copyright 30.07.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minlmrestartfrom(minlmstate state,
+            double[] x)
+        {
+            int i_ = 0;
+
+            alglib.ap.assert(alglib.ap.len(x)>=state.n, "MinLMRestartFrom: Length(X)<N!");
+            alglib.ap.assert(apserv.isfinitevector(x, state.n), "MinLMRestartFrom: X contains infinite or NaN values!");
+            for(i_=0; i_<=state.n-1;i_++)
+            {
+                state.xbase[i_] = x[i_];
+            }
+            state.rstate.ia = new int[4+1];
+            state.rstate.ba = new bool[0+1];
+            state.rstate.ra = new double[2+1];
+            state.rstate.stage = -1;
+            clearrequestfields(state);
+        }
+
+
+        /*************************************************************************
+        This is obsolete function.
+
+        Since ALGLIB 3.3 it is equivalent to MinLMCreateVJ().
+
+          -- ALGLIB --
+             Copyright 30.03.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minlmcreatevgj(int n,
+            int m,
+            double[] x,
+            minlmstate state)
+        {
+            minlmcreatevj(n, m, x, state);
+        }
+
+
+        /*************************************************************************
+        This is obsolete function.
+
+        Since ALGLIB 3.3 it is equivalent to MinLMCreateFJ().
+
+          -- ALGLIB --
+             Copyright 30.03.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minlmcreatefgj(int n,
+            int m,
+            double[] x,
+            minlmstate state)
+        {
+            minlmcreatefj(n, m, x, state);
+        }
+
+
+        /*************************************************************************
+        This function is considered obsolete since ALGLIB 3.1.0 and is present for
+        backward  compatibility  only.  We  recommend  to use MinLMCreateVJ, which
+        provides similar, but more consistent and feature-rich interface.
+
+          -- ALGLIB --
+             Copyright 30.03.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minlmcreatefj(int n,
+            int m,
+            double[] x,
+            minlmstate state)
+        {
+            alglib.ap.assert(n>=1, "MinLMCreateFJ: N<1!");
+            alglib.ap.assert(m>=1, "MinLMCreateFJ: M<1!");
+            alglib.ap.assert(alglib.ap.len(x)>=n, "MinLMCreateFJ: Length(X)<N!");
+            alglib.ap.assert(apserv.isfinitevector(x, n), "MinLMCreateFJ: X contains infinite or NaN values!");
+            
+            //
+            // initialize
+            //
+            state.n = n;
+            state.m = m;
+            state.algomode = 1;
+            state.hasf = true;
+            state.hasfi = false;
+            state.hasg = false;
+            
+            //
+            // init 2
+            //
+            lmprepare(n, m, true, state);
+            minlmsetacctype(state, 0);
+            minlmsetcond(state, 0, 0, 0, 0);
+            minlmsetxrep(state, false);
+            minlmsetstpmax(state, 0);
+            minlmrestartfrom(state, x);
+        }
+
+
+        /*************************************************************************
+        Prepare internal structures (except for RComm).
+
+        Note: M must be zero for FGH mode, non-zero for V/VJ/FJ/FGJ mode.
+        *************************************************************************/
+        private static void lmprepare(int n,
+            int m,
+            bool havegrad,
+            minlmstate state)
+        {
+            int i = 0;
+
+            if( n<=0 || m<0 )
+            {
+                return;
+            }
+            if( havegrad )
+            {
+                state.g = new double[n];
+            }
+            if( m!=0 )
+            {
+                state.j = new double[m, n];
+                state.fi = new double[m];
+                state.fibase = new double[m];
+                state.deltaf = new double[m];
+                state.fm1 = new double[m];
+                state.fp1 = new double[m];
+            }
+            else
+            {
+                state.h = new double[n, n];
+            }
+            state.x = new double[n];
+            state.deltax = new double[n];
+            state.quadraticmodel = new double[n, n];
+            state.xbase = new double[n];
+            state.gbase = new double[n];
+            state.xdir = new double[n];
+            state.tmp0 = new double[n];
+            
+            //
+            // prepare internal L-BFGS
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                state.x[i] = 0;
+            }
+            minlbfgs.minlbfgscreate(n, Math.Min(additers, n), state.x, state.internalstate);
+            minlbfgs.minlbfgssetcond(state.internalstate, 0.0, 0.0, 0.0, Math.Min(additers, n));
+            
+            //
+            // Prepare internal QP solver
+            //
+            minqp.minqpcreate(n, state.qpstate);
+            minqp.minqpsetalgocholesky(state.qpstate);
+            
+            //
+            // Prepare boundary constraints
+            //
+            state.bndl = new double[n];
+            state.bndu = new double[n];
+            state.havebndl = new bool[n];
+            state.havebndu = new bool[n];
+            for(i=0; i<=n-1; i++)
+            {
+                state.bndl[i] = Double.NegativeInfinity;
+                state.havebndl[i] = false;
+                state.bndu[i] = Double.PositiveInfinity;
+                state.havebndu[i] = false;
+            }
+            
+            //
+            // Prepare scaling matrix
+            //
+            state.s = new double[n];
+            for(i=0; i<=n-1; i++)
+            {
+                state.s[i] = 1.0;
+            }
+        }
+
+
+        /*************************************************************************
+        Clears request fileds (to be sure that we don't forgot to clear something)
+        *************************************************************************/
+        private static void clearrequestfields(minlmstate state)
+        {
+            state.needf = false;
+            state.needfg = false;
+            state.needfgh = false;
+            state.needfij = false;
+            state.needfi = false;
+            state.xupdated = false;
+        }
+
+
+        /*************************************************************************
+        Increases lambda, returns False when there is a danger of overflow
+        *************************************************************************/
+        private static bool increaselambda(ref double lambdav,
+            ref double nu)
+        {
+            bool result = new bool();
+            double lnlambda = 0;
+            double lnnu = 0;
+            double lnlambdaup = 0;
+            double lnmax = 0;
+
+            result = false;
+            lnlambda = Math.Log(lambdav);
+            lnlambdaup = Math.Log(lambdaup);
+            lnnu = Math.Log(nu);
+            lnmax = Math.Log(math.maxrealnumber);
+            if( (double)(lnlambda+lnlambdaup+lnnu)>(double)(0.25*lnmax) )
+            {
+                return result;
+            }
+            if( (double)(lnnu+Math.Log(2))>(double)(lnmax) )
+            {
+                return result;
+            }
+            lambdav = lambdav*lambdaup*nu;
+            nu = nu*2;
+            result = true;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Decreases lambda, but leaves it unchanged when there is danger of underflow.
+        *************************************************************************/
+        private static void decreaselambda(ref double lambdav,
+            ref double nu)
+        {
+            nu = 1;
+            if( (double)(Math.Log(lambdav)+Math.Log(lambdadown))<(double)(Math.Log(math.minrealnumber)) )
+            {
+                lambdav = math.minrealnumber;
+            }
+            else
+            {
+                lambdav = lambdav*lambdadown;
+            }
+        }
+
+
+        /*************************************************************************
+        Returns norm of bounded scaled anti-gradient.
+
+        Bounded antigradient is a vector obtained from  anti-gradient  by  zeroing
+        components which point outwards:
+            result = norm(v)
+            v[i]=0     if ((-g[i]<0)and(x[i]=bndl[i])) or
+                          ((-g[i]>0)and(x[i]=bndu[i]))
+            v[i]=-g[i]*s[i] otherwise, where s[i] is a scale for I-th variable
+
+        This function may be used to check a stopping criterion.
+
+          -- ALGLIB --
+             Copyright 14.01.2011 by Bochkanov Sergey
+        *************************************************************************/
+        private static double boundedscaledantigradnorm(minlmstate state,
+            double[] x,
+            double[] g)
+        {
+            double result = 0;
+            int n = 0;
+            int i = 0;
+            double v = 0;
+
+            result = 0;
+            n = state.n;
+            for(i=0; i<=n-1; i++)
+            {
+                v = -(g[i]*state.s[i]);
+                if( state.havebndl[i] )
+                {
+                    if( (double)(x[i])<=(double)(state.bndl[i]) && (double)(-g[i])<(double)(0) )
+                    {
+                        v = 0;
+                    }
+                }
+                if( state.havebndu[i] )
+                {
+                    if( (double)(x[i])>=(double)(state.bndu[i]) && (double)(-g[i])>(double)(0) )
+                    {
+                        v = 0;
+                    }
+                }
+                result = result+math.sqr(v);
+            }
+            result = Math.Sqrt(result);
+            return result;
+        }
+
+
+    }
+    public class mincomp
+    {
+        public class minasastate
+        {
+            public int n;
+            public double epsg;
+            public double epsf;
+            public double epsx;
+            public int maxits;
+            public bool xrep;
+            public double stpmax;
+            public int cgtype;
+            public int k;
+            public int nfev;
+            public int mcstage;
+            public double[] bndl;
+            public double[] bndu;
+            public int curalgo;
+            public int acount;
+            public double mu;
+            public double finit;
+            public double dginit;
+            public double[] ak;
+            public double[] xk;
+            public double[] dk;
+            public double[] an;
+            public double[] xn;
+            public double[] dn;
+            public double[] d;
+            public double fold;
+            public double stp;
+            public double[] work;
+            public double[] yk;
+            public double[] gc;
+            public double laststep;
+            public double[] x;
+            public double f;
+            public double[] g;
+            public bool needfg;
+            public bool xupdated;
+            public rcommstate rstate;
+            public int repiterationscount;
+            public int repnfev;
+            public int repterminationtype;
+            public int debugrestartscount;
+            public linmin.linminstate lstate;
+            public double betahs;
+            public double betady;
+            public minasastate()
+            {
+                bndl = new double[0];
+                bndu = new double[0];
+                ak = new double[0];
+                xk = new double[0];
+                dk = new double[0];
+                an = new double[0];
+                xn = new double[0];
+                dn = new double[0];
+                d = new double[0];
+                work = new double[0];
+                yk = new double[0];
+                gc = new double[0];
+                x = new double[0];
+                g = new double[0];
+                rstate = new rcommstate();
+                lstate = new linmin.linminstate();
+            }
+        };
+
+
+        public class minasareport
+        {
+            public int iterationscount;
+            public int nfev;
+            public int terminationtype;
+            public int activeconstraints;
+        };
+
+
+
+
+        public const int n1 = 2;
+        public const int n2 = 2;
+        public const double stpmin = 1.0E-300;
+        public const double gtol = 0.3;
+        public const double gpaftol = 0.0001;
+        public const double gpadecay = 0.5;
+        public const double asarho = 0.5;
+
+
+        /*************************************************************************
+        Obsolete function, use MinLBFGSSetPrecDefault() instead.
+
+          -- ALGLIB --
+             Copyright 13.10.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minlbfgssetdefaultpreconditioner(minlbfgs.minlbfgsstate state)
+        {
+            minlbfgs.minlbfgssetprecdefault(state);
+        }
+
+
+        /*************************************************************************
+        Obsolete function, use MinLBFGSSetCholeskyPreconditioner() instead.
+
+          -- ALGLIB --
+             Copyright 13.10.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minlbfgssetcholeskypreconditioner(minlbfgs.minlbfgsstate state,
+            double[,] p,
+            bool isupper)
+        {
+            minlbfgs.minlbfgssetpreccholesky(state, p, isupper);
+        }
+
+
+        /*************************************************************************
+        This is obsolete function which was used by previous version of the  BLEIC
+        optimizer. It does nothing in the current version of BLEIC.
+
+          -- ALGLIB --
+             Copyright 28.11.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minbleicsetbarrierwidth(minbleic.minbleicstate state,
+            double mu)
+        {
+        }
+
+
+        /*************************************************************************
+        This is obsolete function which was used by previous version of the  BLEIC
+        optimizer. It does nothing in the current version of BLEIC.
+
+          -- ALGLIB --
+             Copyright 28.11.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minbleicsetbarrierdecay(minbleic.minbleicstate state,
+            double mudecay)
+        {
+        }
+
+
+        /*************************************************************************
+        Obsolete optimization algorithm.
+        Was replaced by MinBLEIC subpackage.
+
+          -- ALGLIB --
+             Copyright 25.03.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minasacreate(int n,
+            double[] x,
+            double[] bndl,
+            double[] bndu,
+            minasastate state)
+        {
+            int i = 0;
+
+            alglib.ap.assert(n>=1, "MinASA: N too small!");
+            alglib.ap.assert(alglib.ap.len(x)>=n, "MinCGCreate: Length(X)<N!");
+            alglib.ap.assert(apserv.isfinitevector(x, n), "MinCGCreate: X contains infinite or NaN values!");
+            alglib.ap.assert(alglib.ap.len(bndl)>=n, "MinCGCreate: Length(BndL)<N!");
+            alglib.ap.assert(apserv.isfinitevector(bndl, n), "MinCGCreate: BndL contains infinite or NaN values!");
+            alglib.ap.assert(alglib.ap.len(bndu)>=n, "MinCGCreate: Length(BndU)<N!");
+            alglib.ap.assert(apserv.isfinitevector(bndu, n), "MinCGCreate: BndU contains infinite or NaN values!");
+            for(i=0; i<=n-1; i++)
+            {
+                alglib.ap.assert((double)(bndl[i])<=(double)(bndu[i]), "MinASA: inconsistent bounds!");
+                alglib.ap.assert((double)(bndl[i])<=(double)(x[i]), "MinASA: infeasible X!");
+                alglib.ap.assert((double)(x[i])<=(double)(bndu[i]), "MinASA: infeasible X!");
+            }
+            
+            //
+            // Initialize
+            //
+            state.n = n;
+            minasasetcond(state, 0, 0, 0, 0);
+            minasasetxrep(state, false);
+            minasasetstpmax(state, 0);
+            minasasetalgorithm(state, -1);
+            state.bndl = new double[n];
+            state.bndu = new double[n];
+            state.ak = new double[n];
+            state.xk = new double[n];
+            state.dk = new double[n];
+            state.an = new double[n];
+            state.xn = new double[n];
+            state.dn = new double[n];
+            state.x = new double[n];
+            state.d = new double[n];
+            state.g = new double[n];
+            state.gc = new double[n];
+            state.work = new double[n];
+            state.yk = new double[n];
+            minasarestartfrom(state, x, bndl, bndu);
+        }
+
+
+        /*************************************************************************
+        Obsolete optimization algorithm.
+        Was replaced by MinBLEIC subpackage.
+
+          -- ALGLIB --
+             Copyright 02.04.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minasasetcond(minasastate state,
+            double epsg,
+            double epsf,
+            double epsx,
+            int maxits)
+        {
+            alglib.ap.assert(math.isfinite(epsg), "MinASASetCond: EpsG is not finite number!");
+            alglib.ap.assert((double)(epsg)>=(double)(0), "MinASASetCond: negative EpsG!");
+            alglib.ap.assert(math.isfinite(epsf), "MinASASetCond: EpsF is not finite number!");
+            alglib.ap.assert((double)(epsf)>=(double)(0), "MinASASetCond: negative EpsF!");
+            alglib.ap.assert(math.isfinite(epsx), "MinASASetCond: EpsX is not finite number!");
+            alglib.ap.assert((double)(epsx)>=(double)(0), "MinASASetCond: negative EpsX!");
+            alglib.ap.assert(maxits>=0, "MinASASetCond: negative MaxIts!");
+            if( (((double)(epsg)==(double)(0) && (double)(epsf)==(double)(0)) && (double)(epsx)==(double)(0)) && maxits==0 )
+            {
+                epsx = 1.0E-6;
+            }
+            state.epsg = epsg;
+            state.epsf = epsf;
+            state.epsx = epsx;
+            state.maxits = maxits;
+        }
+
+
+        /*************************************************************************
+        Obsolete optimization algorithm.
+        Was replaced by MinBLEIC subpackage.
+
+          -- ALGLIB --
+             Copyright 02.04.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minasasetxrep(minasastate state,
+            bool needxrep)
+        {
+            state.xrep = needxrep;
+        }
+
+
+        /*************************************************************************
+        Obsolete optimization algorithm.
+        Was replaced by MinBLEIC subpackage.
+
+          -- ALGLIB --
+             Copyright 02.04.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minasasetalgorithm(minasastate state,
+            int algotype)
+        {
+            alglib.ap.assert(algotype>=-1 && algotype<=1, "MinASASetAlgorithm: incorrect AlgoType!");
+            if( algotype==-1 )
+            {
+                algotype = 1;
+            }
+            state.cgtype = algotype;
+        }
+
+
+        /*************************************************************************
+        Obsolete optimization algorithm.
+        Was replaced by MinBLEIC subpackage.
+
+          -- ALGLIB --
+             Copyright 02.04.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minasasetstpmax(minasastate state,
+            double stpmax)
+        {
+            alglib.ap.assert(math.isfinite(stpmax), "MinASASetStpMax: StpMax is not finite!");
+            alglib.ap.assert((double)(stpmax)>=(double)(0), "MinASASetStpMax: StpMax<0!");
+            state.stpmax = stpmax;
+        }
+
+
+        /*************************************************************************
+
+          -- ALGLIB --
+             Copyright 20.03.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static bool minasaiteration(minasastate state)
+        {
+            bool result = new bool();
+            int n = 0;
+            int i = 0;
+            double betak = 0;
+            double v = 0;
+            double vv = 0;
+            int mcinfo = 0;
+            bool b = new bool();
+            bool stepfound = new bool();
+            int diffcnt = 0;
+            int i_ = 0;
+
+            
+            //
+            // Reverse communication preparations
+            // I know it looks ugly, but it works the same way
+            // anywhere from C++ to Python.
+            //
+            // This code initializes locals by:
+            // * random values determined during code
+            //   generation - on first subroutine call
+            // * values from previous call - on subsequent calls
+            //
+            if( state.rstate.stage>=0 )
+            {
+                n = state.rstate.ia[0];
+                i = state.rstate.ia[1];
+                mcinfo = state.rstate.ia[2];
+                diffcnt = state.rstate.ia[3];
+                b = state.rstate.ba[0];
+                stepfound = state.rstate.ba[1];
+                betak = state.rstate.ra[0];
+                v = state.rstate.ra[1];
+                vv = state.rstate.ra[2];
+            }
+            else
+            {
+                n = -983;
+                i = -989;
+                mcinfo = -834;
+                diffcnt = 900;
+                b = true;
+                stepfound = false;
+                betak = 214;
+                v = -338;
+                vv = -686;
+            }
+            if( state.rstate.stage==0 )
+            {
+                goto lbl_0;
+            }
+            if( state.rstate.stage==1 )
+            {
+                goto lbl_1;
+            }
+            if( state.rstate.stage==2 )
+            {
+                goto lbl_2;
+            }
+            if( state.rstate.stage==3 )
+            {
+                goto lbl_3;
+            }
+            if( state.rstate.stage==4 )
+            {
+                goto lbl_4;
+            }
+            if( state.rstate.stage==5 )
+            {
+                goto lbl_5;
+            }
+            if( state.rstate.stage==6 )
+            {
+                goto lbl_6;
+            }
+            if( state.rstate.stage==7 )
+            {
+                goto lbl_7;
+            }
+            if( state.rstate.stage==8 )
+            {
+                goto lbl_8;
+            }
+            if( state.rstate.stage==9 )
+            {
+                goto lbl_9;
+            }
+            if( state.rstate.stage==10 )
+            {
+                goto lbl_10;
+            }
+            if( state.rstate.stage==11 )
+            {
+                goto lbl_11;
+            }
+            if( state.rstate.stage==12 )
+            {
+                goto lbl_12;
+            }
+            if( state.rstate.stage==13 )
+            {
+                goto lbl_13;
+            }
+            if( state.rstate.stage==14 )
+            {
+                goto lbl_14;
+            }
+            
+            //
+            // Routine body
+            //
+            
+            //
+            // Prepare
+            //
+            n = state.n;
+            state.repterminationtype = 0;
+            state.repiterationscount = 0;
+            state.repnfev = 0;
+            state.debugrestartscount = 0;
+            state.cgtype = 1;
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.xk[i_] = state.x[i_];
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                if( (double)(state.xk[i])==(double)(state.bndl[i]) || (double)(state.xk[i])==(double)(state.bndu[i]) )
+                {
+                    state.ak[i] = 0;
+                }
+                else
+                {
+                    state.ak[i] = 1;
+                }
+            }
+            state.mu = 0.1;
+            state.curalgo = 0;
+            
+            //
+            // Calculate F/G, initialize algorithm
+            //
+            clearrequestfields(state);
+            state.needfg = true;
+            state.rstate.stage = 0;
+            goto lbl_rcomm;
+        lbl_0:
+            state.needfg = false;
+            if( !state.xrep )
+            {
+                goto lbl_15;
+            }
+            
+            //
+            // progress report
+            //
+            clearrequestfields(state);
+            state.xupdated = true;
+            state.rstate.stage = 1;
+            goto lbl_rcomm;
+        lbl_1:
+            state.xupdated = false;
+        lbl_15:
+            if( (double)(asaboundedantigradnorm(state))<=(double)(state.epsg) )
+            {
+                state.repterminationtype = 4;
+                result = false;
+                return result;
+            }
+            state.repnfev = state.repnfev+1;
+            
+            //
+            // Main cycle
+            //
+            // At the beginning of new iteration:
+            // * CurAlgo stores current algorithm selector
+            // * State.XK, State.F and State.G store current X/F/G
+            // * State.AK stores current set of active constraints
+            //
+        lbl_17:
+            if( false )
+            {
+                goto lbl_18;
+            }
+            
+            //
+            // GPA algorithm
+            //
+            if( state.curalgo!=0 )
+            {
+                goto lbl_19;
+            }
+            state.k = 0;
+            state.acount = 0;
+        lbl_21:
+            if( false )
+            {
+                goto lbl_22;
+            }
+            
+            //
+            // Determine Dk = proj(xk - gk)-xk
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                state.d[i] = apserv.boundval(state.xk[i]-state.g[i], state.bndl[i], state.bndu[i])-state.xk[i];
+            }
+            
+            //
+            // Armijo line search.
+            // * exact search with alpha=1 is tried first,
+            //   'exact' means that we evaluate f() EXACTLY at
+            //   bound(x-g,bndl,bndu), without intermediate floating
+            //   point operations.
+            // * alpha<1 are tried if explicit search wasn't successful
+            // Result is placed into XN.
+            //
+            // Two types of search are needed because we can't
+            // just use second type with alpha=1 because in finite
+            // precision arithmetics (x1-x0)+x0 may differ from x1.
+            // So while x1 is correctly bounded (it lie EXACTLY on
+            // boundary, if it is active), (x1-x0)+x0 may be
+            // not bounded.
+            //
+            v = 0.0;
+            for(i_=0; i_<=n-1;i_++)
+            {
+                v += state.d[i_]*state.g[i_];
+            }
+            state.dginit = v;
+            state.finit = state.f;
+            if( !((double)(asad1norm(state))<=(double)(state.stpmax) || (double)(state.stpmax)==(double)(0)) )
+            {
+                goto lbl_23;
+            }
+            
+            //
+            // Try alpha=1 step first
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                state.x[i] = apserv.boundval(state.xk[i]-state.g[i], state.bndl[i], state.bndu[i]);
+            }
+            clearrequestfields(state);
+            state.needfg = true;
+            state.rstate.stage = 2;
+            goto lbl_rcomm;
+        lbl_2:
+            state.needfg = false;
+            state.repnfev = state.repnfev+1;
+            stepfound = (double)(state.f)<=(double)(state.finit+gpaftol*state.dginit);
+            goto lbl_24;
+        lbl_23:
+            stepfound = false;
+        lbl_24:
+            if( !stepfound )
+            {
+                goto lbl_25;
+            }
+            
+            //
+            // we are at the boundary(ies)
+            //
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.xn[i_] = state.x[i_];
+            }
+            state.stp = 1;
+            goto lbl_26;
+        lbl_25:
+            
+            //
+            // alpha=1 is too large, try smaller values
+            //
+            state.stp = 1;
+            linmin.linminnormalized(ref state.d, ref state.stp, n);
+            state.dginit = state.dginit/state.stp;
+            state.stp = gpadecay*state.stp;
+            if( (double)(state.stpmax)>(double)(0) )
+            {
+                state.stp = Math.Min(state.stp, state.stpmax);
+            }
+        lbl_27:
+            if( false )
+            {
+                goto lbl_28;
+            }
+            v = state.stp;
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.xk[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.x[i_] + v*state.d[i_];
+            }
+            clearrequestfields(state);
+            state.needfg = true;
+            state.rstate.stage = 3;
+            goto lbl_rcomm;
+        lbl_3:
+            state.needfg = false;
+            state.repnfev = state.repnfev+1;
+            if( (double)(state.stp)<=(double)(stpmin) )
+            {
+                goto lbl_28;
+            }
+            if( (double)(state.f)<=(double)(state.finit+state.stp*gpaftol*state.dginit) )
+            {
+                goto lbl_28;
+            }
+            state.stp = state.stp*gpadecay;
+            goto lbl_27;
+        lbl_28:
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.xn[i_] = state.x[i_];
+            }
+        lbl_26:
+            state.repiterationscount = state.repiterationscount+1;
+            if( !state.xrep )
+            {
+                goto lbl_29;
+            }
+            
+            //
+            // progress report
+            //
+            clearrequestfields(state);
+            state.xupdated = true;
+            state.rstate.stage = 4;
+            goto lbl_rcomm;
+        lbl_4:
+            state.xupdated = false;
+        lbl_29:
+            
+            //
+            // Calculate new set of active constraints.
+            // Reset counter if active set was changed.
+            // Prepare for the new iteration
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                if( (double)(state.xn[i])==(double)(state.bndl[i]) || (double)(state.xn[i])==(double)(state.bndu[i]) )
+                {
+                    state.an[i] = 0;
+                }
+                else
+                {
+                    state.an[i] = 1;
+                }
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                if( (double)(state.ak[i])!=(double)(state.an[i]) )
+                {
+                    state.acount = -1;
+                    break;
+                }
+            }
+            state.acount = state.acount+1;
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.xk[i_] = state.xn[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.ak[i_] = state.an[i_];
+            }
+            
+            //
+            // Stopping conditions
+            //
+            if( !(state.repiterationscount>=state.maxits && state.maxits>0) )
+            {
+                goto lbl_31;
+            }
+            
+            //
+            // Too many iterations
+            //
+            state.repterminationtype = 5;
+            if( !state.xrep )
+            {
+                goto lbl_33;
+            }
+            clearrequestfields(state);
+            state.xupdated = true;
+            state.rstate.stage = 5;
+            goto lbl_rcomm;
+        lbl_5:
+            state.xupdated = false;
+        lbl_33:
+            result = false;
+            return result;
+        lbl_31:
+            if( (double)(asaboundedantigradnorm(state))>(double)(state.epsg) )
+            {
+                goto lbl_35;
+            }
+            
+            //
+            // Gradient is small enough
+            //
+            state.repterminationtype = 4;
+            if( !state.xrep )
+            {
+                goto lbl_37;
+            }
+            clearrequestfields(state);
+            state.xupdated = true;
+            state.rstate.stage = 6;
+            goto lbl_rcomm;
+        lbl_6:
+            state.xupdated = false;
+        lbl_37:
+            result = false;
+            return result;
+        lbl_35:
+            v = 0.0;
+            for(i_=0; i_<=n-1;i_++)
+            {
+                v += state.d[i_]*state.d[i_];
+            }
+            if( (double)(Math.Sqrt(v)*state.stp)>(double)(state.epsx) )
+            {
+                goto lbl_39;
+            }
+            
+            //
+            // Step size is too small, no further improvement is
+            // possible
+            //
+            state.repterminationtype = 2;
+            if( !state.xrep )
+            {
+                goto lbl_41;
+            }
+            clearrequestfields(state);
+            state.xupdated = true;
+            state.rstate.stage = 7;
+            goto lbl_rcomm;
+        lbl_7:
+            state.xupdated = false;
+        lbl_41:
+            result = false;
+            return result;
+        lbl_39:
+            if( (double)(state.finit-state.f)>(double)(state.epsf*Math.Max(Math.Abs(state.finit), Math.Max(Math.Abs(state.f), 1.0))) )
+            {
+                goto lbl_43;
+            }
+            
+            //
+            // F(k+1)-F(k) is small enough
+            //
+            state.repterminationtype = 1;
+            if( !state.xrep )
+            {
+                goto lbl_45;
+            }
+            clearrequestfields(state);
+            state.xupdated = true;
+            state.rstate.stage = 8;
+            goto lbl_rcomm;
+        lbl_8:
+            state.xupdated = false;
+        lbl_45:
+            result = false;
+            return result;
+        lbl_43:
+            
+            //
+            // Decide - should we switch algorithm or not
+            //
+            if( asauisempty(state) )
+            {
+                if( (double)(asaginorm(state))>=(double)(state.mu*asad1norm(state)) )
+                {
+                    state.curalgo = 1;
+                    goto lbl_22;
+                }
+                else
+                {
+                    state.mu = state.mu*asarho;
+                }
+            }
+            else
+            {
+                if( state.acount==n1 )
+                {
+                    if( (double)(asaginorm(state))>=(double)(state.mu*asad1norm(state)) )
+                    {
+                        state.curalgo = 1;
+                        goto lbl_22;
+                    }
+                }
+            }
+            
+            //
+            // Next iteration
+            //
+            state.k = state.k+1;
+            goto lbl_21;
+        lbl_22:
+        lbl_19:
+            
+            //
+            // CG algorithm
+            //
+            if( state.curalgo!=1 )
+            {
+                goto lbl_47;
+            }
+            
+            //
+            // first, check that there are non-active constraints.
+            // move to GPA algorithm, if all constraints are active
+            //
+            b = true;
+            for(i=0; i<=n-1; i++)
+            {
+                if( (double)(state.ak[i])!=(double)(0) )
+                {
+                    b = false;
+                    break;
+                }
+            }
+            if( b )
+            {
+                state.curalgo = 0;
+                goto lbl_17;
+            }
+            
+            //
+            // CG iterations
+            //
+            state.fold = state.f;
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.xk[i_] = state.x[i_];
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                state.dk[i] = -(state.g[i]*state.ak[i]);
+                state.gc[i] = state.g[i]*state.ak[i];
+            }
+        lbl_49:
+            if( false )
+            {
+                goto lbl_50;
+            }
+            
+            //
+            // Store G[k] for later calculation of Y[k]
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                state.yk[i] = -state.gc[i];
+            }
+            
+            //
+            // Make a CG step in direction given by DK[]:
+            // * calculate step. Step projection into feasible set
+            //   is used. It has several benefits: a) step may be
+            //   found with usual line search, b) multiple constraints
+            //   may be activated with one step, c) activated constraints
+            //   are detected in a natural way - just compare x[i] with
+            //   bounds
+            // * update active set, set B to True, if there
+            //   were changes in the set.
+            //
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.d[i_] = state.dk[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.xn[i_] = state.xk[i_];
+            }
+            state.mcstage = 0;
+            state.stp = 1;
+            linmin.linminnormalized(ref state.d, ref state.stp, n);
+            if( (double)(state.laststep)!=(double)(0) )
+            {
+                state.stp = state.laststep;
+            }
+            linmin.mcsrch(n, ref state.xn, ref state.f, ref state.gc, state.d, ref state.stp, state.stpmax, gtol, ref mcinfo, ref state.nfev, ref state.work, state.lstate, ref state.mcstage);
+        lbl_51:
+            if( state.mcstage==0 )
+            {
+                goto lbl_52;
+            }
+            
+            //
+            // preprocess data: bound State.XN so it belongs to the
+            // feasible set and store it in the State.X
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                state.x[i] = apserv.boundval(state.xn[i], state.bndl[i], state.bndu[i]);
+            }
+            
+            //
+            // RComm
+            //
+            clearrequestfields(state);
+            state.needfg = true;
+            state.rstate.stage = 9;
+            goto lbl_rcomm;
+        lbl_9:
+            state.needfg = false;
+            
+            //
+            // postprocess data: zero components of G corresponding to
+            // the active constraints
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                if( (double)(state.x[i])==(double)(state.bndl[i]) || (double)(state.x[i])==(double)(state.bndu[i]) )
+                {
+                    state.gc[i] = 0;
+                }
+                else
+                {
+                    state.gc[i] = state.g[i];
+                }
+            }
+            linmin.mcsrch(n, ref state.xn, ref state.f, ref state.gc, state.d, ref state.stp, state.stpmax, gtol, ref mcinfo, ref state.nfev, ref state.work, state.lstate, ref state.mcstage);
+            goto lbl_51;
+        lbl_52:
+            diffcnt = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                
+                //
+                // XN contains unprojected result, project it,
+                // save copy to X (will be used for progress reporting)
+                //
+                state.xn[i] = apserv.boundval(state.xn[i], state.bndl[i], state.bndu[i]);
+                
+                //
+                // update active set
+                //
+                if( (double)(state.xn[i])==(double)(state.bndl[i]) || (double)(state.xn[i])==(double)(state.bndu[i]) )
+                {
+                    state.an[i] = 0;
+                }
+                else
+                {
+                    state.an[i] = 1;
+                }
+                if( (double)(state.an[i])!=(double)(state.ak[i]) )
+                {
+                    diffcnt = diffcnt+1;
+                }
+                state.ak[i] = state.an[i];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.xk[i_] = state.xn[i_];
+            }
+            state.repnfev = state.repnfev+state.nfev;
+            state.repiterationscount = state.repiterationscount+1;
+            if( !state.xrep )
+            {
+                goto lbl_53;
+            }
+            
+            //
+            // progress report
+            //
+            clearrequestfields(state);
+            state.xupdated = true;
+            state.rstate.stage = 10;
+            goto lbl_rcomm;
+        lbl_10:
+            state.xupdated = false;
+        lbl_53:
+            
+            //
+            // Update info about step length
+            //
+            v = 0.0;
+            for(i_=0; i_<=n-1;i_++)
+            {
+                v += state.d[i_]*state.d[i_];
+            }
+            state.laststep = Math.Sqrt(v)*state.stp;
+            
+            //
+            // Check stopping conditions.
+            //
+            if( (double)(asaboundedantigradnorm(state))>(double)(state.epsg) )
+            {
+                goto lbl_55;
+            }
+            
+            //
+            // Gradient is small enough
+            //
+            state.repterminationtype = 4;
+            if( !state.xrep )
+            {
+                goto lbl_57;
+            }
+            clearrequestfields(state);
+            state.xupdated = true;
+            state.rstate.stage = 11;
+            goto lbl_rcomm;
+        lbl_11:
+            state.xupdated = false;
+        lbl_57:
+            result = false;
+            return result;
+        lbl_55:
+            if( !(state.repiterationscount>=state.maxits && state.maxits>0) )
+            {
+                goto lbl_59;
+            }
+            
+            //
+            // Too many iterations
+            //
+            state.repterminationtype = 5;
+            if( !state.xrep )
+            {
+                goto lbl_61;
+            }
+            clearrequestfields(state);
+            state.xupdated = true;
+            state.rstate.stage = 12;
+            goto lbl_rcomm;
+        lbl_12:
+            state.xupdated = false;
+        lbl_61:
+            result = false;
+            return result;
+        lbl_59:
+            if( !((double)(asaginorm(state))>=(double)(state.mu*asad1norm(state)) && diffcnt==0) )
+            {
+                goto lbl_63;
+            }
+            
+            //
+            // These conditions (EpsF/EpsX) are explicitly or implicitly
+            // related to the current step size and influenced
+            // by changes in the active constraints.
+            //
+            // For these reasons they are checked only when we don't
+            // want to 'unstick' at the end of the iteration and there
+            // were no changes in the active set.
+            //
+            // NOTE: consition |G|>=Mu*|D1| must be exactly opposite
+            // to the condition used to switch back to GPA. At least
+            // one inequality must be strict, otherwise infinite cycle
+            // may occur when |G|=Mu*|D1| (we DON'T test stopping
+            // conditions and we DON'T switch to GPA, so we cycle
+            // indefinitely).
+            //
+            if( (double)(state.fold-state.f)>(double)(state.epsf*Math.Max(Math.Abs(state.fold), Math.Max(Math.Abs(state.f), 1.0))) )
+            {
+                goto lbl_65;
+            }
+            
+            //
+            // F(k+1)-F(k) is small enough
+            //
+            state.repterminationtype = 1;
+            if( !state.xrep )
+            {
+                goto lbl_67;
+            }
+            clearrequestfields(state);
+            state.xupdated = true;
+            state.rstate.stage = 13;
+            goto lbl_rcomm;
+        lbl_13:
+            state.xupdated = false;
+        lbl_67:
+            result = false;
+            return result;
+        lbl_65:
+            if( (double)(state.laststep)>(double)(state.epsx) )
+            {
+                goto lbl_69;
+            }
+            
+            //
+            // X(k+1)-X(k) is small enough
+            //
+            state.repterminationtype = 2;
+            if( !state.xrep )
+            {
+                goto lbl_71;
+            }
+            clearrequestfields(state);
+            state.xupdated = true;
+            state.rstate.stage = 14;
+            goto lbl_rcomm;
+        lbl_14:
+            state.xupdated = false;
+        lbl_71:
+            result = false;
+            return result;
+        lbl_69:
+        lbl_63:
+            
+            //
+            // Check conditions for switching
+            //
+            if( (double)(asaginorm(state))<(double)(state.mu*asad1norm(state)) )
+            {
+                state.curalgo = 0;
+                goto lbl_50;
+            }
+            if( diffcnt>0 )
+            {
+                if( asauisempty(state) || diffcnt>=n2 )
+                {
+                    state.curalgo = 1;
+                }
+                else
+                {
+                    state.curalgo = 0;
+                }
+                goto lbl_50;
+            }
+            
+            //
+            // Calculate D(k+1)
+            //
+            // Line search may result in:
+            // * maximum feasible step being taken (already processed)
+            // * point satisfying Wolfe conditions
+            // * some kind of error (CG is restarted by assigning 0.0 to Beta)
+            //
+            if( mcinfo==1 )
+            {
+                
+                //
+                // Standard Wolfe conditions are satisfied:
+                // * calculate Y[K] and BetaK
+                //
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    state.yk[i_] = state.yk[i_] + state.gc[i_];
+                }
+                vv = 0.0;
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    vv += state.yk[i_]*state.dk[i_];
+                }
+                v = 0.0;
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    v += state.gc[i_]*state.gc[i_];
+                }
+                state.betady = v/vv;
+                v = 0.0;
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    v += state.gc[i_]*state.yk[i_];
+                }
+                state.betahs = v/vv;
+                if( state.cgtype==0 )
+                {
+                    betak = state.betady;
+                }
+                if( state.cgtype==1 )
+                {
+                    betak = Math.Max(0, Math.Min(state.betady, state.betahs));
+                }
+            }
+            else
+            {
+                
+                //
+                // Something is wrong (may be function is too wild or too flat).
+                //
+                // We'll set BetaK=0, which will restart CG algorithm.
+                // We can stop later (during normal checks) if stopping conditions are met.
+                //
+                betak = 0;
+                state.debugrestartscount = state.debugrestartscount+1;
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.dn[i_] = -state.gc[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.dn[i_] = state.dn[i_] + betak*state.dk[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.dk[i_] = state.dn[i_];
+            }
+            
+            //
+            // update other information
+            //
+            state.fold = state.f;
+            state.k = state.k+1;
+            goto lbl_49;
+        lbl_50:
+        lbl_47:
+            goto lbl_17;
+        lbl_18:
+            result = false;
+            return result;
+            
+            //
+            // Saving state
+            //
+        lbl_rcomm:
+            result = true;
+            state.rstate.ia[0] = n;
+            state.rstate.ia[1] = i;
+            state.rstate.ia[2] = mcinfo;
+            state.rstate.ia[3] = diffcnt;
+            state.rstate.ba[0] = b;
+            state.rstate.ba[1] = stepfound;
+            state.rstate.ra[0] = betak;
+            state.rstate.ra[1] = v;
+            state.rstate.ra[2] = vv;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Obsolete optimization algorithm.
+        Was replaced by MinBLEIC subpackage.
+
+          -- ALGLIB --
+             Copyright 20.03.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minasaresults(minasastate state,
+            ref double[] x,
+            minasareport rep)
+        {
+            x = new double[0];
+
+            minasaresultsbuf(state, ref x, rep);
+        }
+
+
+        /*************************************************************************
+        Obsolete optimization algorithm.
+        Was replaced by MinBLEIC subpackage.
+
+          -- ALGLIB --
+             Copyright 20.03.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minasaresultsbuf(minasastate state,
+            ref double[] x,
+            minasareport rep)
+        {
+            int i = 0;
+            int i_ = 0;
+
+            if( alglib.ap.len(x)<state.n )
+            {
+                x = new double[state.n];
+            }
+            for(i_=0; i_<=state.n-1;i_++)
+            {
+                x[i_] = state.x[i_];
+            }
+            rep.iterationscount = state.repiterationscount;
+            rep.nfev = state.repnfev;
+            rep.terminationtype = state.repterminationtype;
+            rep.activeconstraints = 0;
+            for(i=0; i<=state.n-1; i++)
+            {
+                if( (double)(state.ak[i])==(double)(0) )
+                {
+                    rep.activeconstraints = rep.activeconstraints+1;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Obsolete optimization algorithm.
+        Was replaced by MinBLEIC subpackage.
+
+          -- ALGLIB --
+             Copyright 30.07.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minasarestartfrom(minasastate state,
+            double[] x,
+            double[] bndl,
+            double[] bndu)
+        {
+            int i_ = 0;
+
+            alglib.ap.assert(alglib.ap.len(x)>=state.n, "MinASARestartFrom: Length(X)<N!");
+            alglib.ap.assert(apserv.isfinitevector(x, state.n), "MinASARestartFrom: X contains infinite or NaN values!");
+            alglib.ap.assert(alglib.ap.len(bndl)>=state.n, "MinASARestartFrom: Length(BndL)<N!");
+            alglib.ap.assert(apserv.isfinitevector(bndl, state.n), "MinASARestartFrom: BndL contains infinite or NaN values!");
+            alglib.ap.assert(alglib.ap.len(bndu)>=state.n, "MinASARestartFrom: Length(BndU)<N!");
+            alglib.ap.assert(apserv.isfinitevector(bndu, state.n), "MinASARestartFrom: BndU contains infinite or NaN values!");
+            for(i_=0; i_<=state.n-1;i_++)
+            {
+                state.x[i_] = x[i_];
+            }
+            for(i_=0; i_<=state.n-1;i_++)
+            {
+                state.bndl[i_] = bndl[i_];
+            }
+            for(i_=0; i_<=state.n-1;i_++)
+            {
+                state.bndu[i_] = bndu[i_];
+            }
+            state.laststep = 0;
+            state.rstate.ia = new int[3+1];
+            state.rstate.ba = new bool[1+1];
+            state.rstate.ra = new double[2+1];
+            state.rstate.stage = -1;
+            clearrequestfields(state);
+        }
+
+
+        /*************************************************************************
+        Returns norm of bounded anti-gradient.
+
+        Bounded antigradient is a vector obtained from  anti-gradient  by  zeroing
+        components which point outwards:
+            result = norm(v)
+            v[i]=0     if ((-g[i]<0)and(x[i]=bndl[i])) or
+                          ((-g[i]>0)and(x[i]=bndu[i]))
+            v[i]=-g[i] otherwise
+
+        This function may be used to check a stopping criterion.
+
+          -- ALGLIB --
+             Copyright 20.03.2009 by Bochkanov Sergey
+        *************************************************************************/
+        private static double asaboundedantigradnorm(minasastate state)
+        {
+            double result = 0;
+            int i = 0;
+            double v = 0;
+
+            result = 0;
+            for(i=0; i<=state.n-1; i++)
+            {
+                v = -state.g[i];
+                if( (double)(state.x[i])==(double)(state.bndl[i]) && (double)(-state.g[i])<(double)(0) )
+                {
+                    v = 0;
+                }
+                if( (double)(state.x[i])==(double)(state.bndu[i]) && (double)(-state.g[i])>(double)(0) )
+                {
+                    v = 0;
+                }
+                result = result+math.sqr(v);
+            }
+            result = Math.Sqrt(result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Returns norm of GI(x).
+
+        GI(x) is  a  gradient  vector  whose  components  associated  with  active
+        constraints are zeroed. It  differs  from  bounded  anti-gradient  because
+        components  of   GI(x)   are   zeroed  independently  of  sign(g[i]),  and
+        anti-gradient's components are zeroed with respect to both constraint  and
+        sign.
+
+          -- ALGLIB --
+             Copyright 20.03.2009 by Bochkanov Sergey
+        *************************************************************************/
+        private static double asaginorm(minasastate state)
+        {
+            double result = 0;
+            int i = 0;
+
+            result = 0;
+            for(i=0; i<=state.n-1; i++)
+            {
+                if( (double)(state.x[i])!=(double)(state.bndl[i]) && (double)(state.x[i])!=(double)(state.bndu[i]) )
+                {
+                    result = result+math.sqr(state.g[i]);
+                }
+            }
+            result = Math.Sqrt(result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Returns norm(D1(State.X))
+
+        For a meaning of D1 see 'NEW ACTIVE SET ALGORITHM FOR BOX CONSTRAINED
+        OPTIMIZATION' by WILLIAM W. HAGER AND HONGCHAO ZHANG.
+
+          -- ALGLIB --
+             Copyright 20.03.2009 by Bochkanov Sergey
+        *************************************************************************/
+        private static double asad1norm(minasastate state)
+        {
+            double result = 0;
+            int i = 0;
+
+            result = 0;
+            for(i=0; i<=state.n-1; i++)
+            {
+                result = result+math.sqr(apserv.boundval(state.x[i]-state.g[i], state.bndl[i], state.bndu[i])-state.x[i]);
+            }
+            result = Math.Sqrt(result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Returns True, if U set is empty.
+
+        * State.X is used as point,
+        * State.G - as gradient,
+        * D is calculated within function (because State.D may have different
+          meaning depending on current optimization algorithm)
+
+        For a meaning of U see 'NEW ACTIVE SET ALGORITHM FOR BOX CONSTRAINED
+        OPTIMIZATION' by WILLIAM W. HAGER AND HONGCHAO ZHANG.
+
+          -- ALGLIB --
+             Copyright 20.03.2009 by Bochkanov Sergey
+        *************************************************************************/
+        private static bool asauisempty(minasastate state)
+        {
+            bool result = new bool();
+            int i = 0;
+            double d = 0;
+            double d2 = 0;
+            double d32 = 0;
+
+            d = asad1norm(state);
+            d2 = Math.Sqrt(d);
+            d32 = d*d2;
+            result = true;
+            for(i=0; i<=state.n-1; i++)
+            {
+                if( (double)(Math.Abs(state.g[i]))>=(double)(d2) && (double)(Math.Min(state.x[i]-state.bndl[i], state.bndu[i]-state.x[i]))>=(double)(d32) )
+                {
+                    result = false;
+                    return result;
+                }
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        Clears request fileds (to be sure that we don't forgot to clear something)
+        *************************************************************************/
+        private static void clearrequestfields(minasastate state)
+        {
+            state.needfg = false;
+            state.xupdated = false;
+        }
+
+
+    }
+}
+
Index: /trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.5.0/ALGLIB-3.5.0/solvers.cs
===================================================================
--- /trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.5.0/ALGLIB-3.5.0/solvers.cs	(revision 7673)
+++ /trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.5.0/ALGLIB-3.5.0/solvers.cs	(revision 7673)
@@ -0,0 +1,7288 @@
+/*************************************************************************
+Copyright (c) Sergey Bochkanov (ALGLIB project).
+
+>>> SOURCE LICENSE >>>
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation (www.fsf.org); either version 2 of the
+License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+A copy of the GNU General Public License is available at
+http://www.fsf.org/licensing/licenses
+>>> END OF LICENSE >>>
+*************************************************************************/
+#pragma warning disable 162
+#pragma warning disable 219
+using System;
+
+public partial class alglib
+{
+
+
+    /*************************************************************************
+
+    *************************************************************************/
+    public class densesolverreport
+    {
+        //
+        // Public declarations
+        //
+        public double r1 { get { return _innerobj.r1; } set { _innerobj.r1 = value; } }
+        public double rinf { get { return _innerobj.rinf; } set { _innerobj.rinf = value; } }
+
+        public densesolverreport()
+        {
+            _innerobj = new densesolver.densesolverreport();
+        }
+
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private densesolver.densesolverreport _innerobj;
+        public densesolver.densesolverreport innerobj { get { return _innerobj; } }
+        public densesolverreport(densesolver.densesolverreport obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+
+    /*************************************************************************
+
+    *************************************************************************/
+    public class densesolverlsreport
+    {
+        //
+        // Public declarations
+        //
+        public double r2 { get { return _innerobj.r2; } set { _innerobj.r2 = value; } }
+        public double[,] cx { get { return _innerobj.cx; } set { _innerobj.cx = value; } }
+        public int n { get { return _innerobj.n; } set { _innerobj.n = value; } }
+        public int k { get { return _innerobj.k; } set { _innerobj.k = value; } }
+
+        public densesolverlsreport()
+        {
+            _innerobj = new densesolver.densesolverlsreport();
+        }
+
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private densesolver.densesolverlsreport _innerobj;
+        public densesolver.densesolverlsreport innerobj { get { return _innerobj; } }
+        public densesolverlsreport(densesolver.densesolverlsreport obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+    /*************************************************************************
+    Dense solver.
+
+    This  subroutine  solves  a  system  A*x=b,  where A is NxN non-denegerate
+    real matrix, x and b are vectors.
+
+    Algorithm features:
+    * automatic detection of degenerate cases
+    * condition number estimation
+    * iterative refinement
+    * O(N^3) complexity
+
+    INPUT PARAMETERS
+        A       -   array[0..N-1,0..N-1], system matrix
+        N       -   size of A
+        B       -   array[0..N-1], right part
+
+    OUTPUT PARAMETERS
+        Info    -   return code:
+                    * -3    A is singular, or VERY close to singular.
+                            X is filled by zeros in such cases.
+                    * -1    N<=0 was passed
+                    *  1    task is solved (but matrix A may be ill-conditioned,
+                            check R1/RInf parameters for condition numbers).
+        Rep     -   solver report, see below for more info
+        X       -   array[0..N-1], it contains:
+                    * solution of A*x=b if A is non-singular (well-conditioned
+                      or ill-conditioned, but not very close to singular)
+                    * zeros,  if  A  is  singular  or  VERY  close to singular
+                      (in this case Info=-3).
+
+    SOLVER REPORT
+
+    Subroutine sets following fields of the Rep structure:
+    * R1        reciprocal of condition number: 1/cond(A), 1-norm.
+    * RInf      reciprocal of condition number: 1/cond(A), inf-norm.
+
+      -- ALGLIB --
+         Copyright 27.01.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void rmatrixsolve(double[,] a, int n, double[] b, out int info, out densesolverreport rep, out double[] x)
+    {
+        info = 0;
+        rep = new densesolverreport();
+        x = new double[0];
+        densesolver.rmatrixsolve(a, n, b, ref info, rep.innerobj, ref x);
+        return;
+    }
+
+    /*************************************************************************
+    Dense solver.
+
+    Similar to RMatrixSolve() but solves task with multiple right parts (where
+    b and x are NxM matrices).
+
+    Algorithm features:
+    * automatic detection of degenerate cases
+    * condition number estimation
+    * optional iterative refinement
+    * O(N^3+M*N^2) complexity
+
+    INPUT PARAMETERS
+        A       -   array[0..N-1,0..N-1], system matrix
+        N       -   size of A
+        B       -   array[0..N-1,0..M-1], right part
+        M       -   right part size
+        RFS     -   iterative refinement switch:
+                    * True - refinement is used.
+                      Less performance, more precision.
+                    * False - refinement is not used.
+                      More performance, less precision.
+
+    OUTPUT PARAMETERS
+        Info    -   same as in RMatrixSolve
+        Rep     -   same as in RMatrixSolve
+        X       -   same as in RMatrixSolve
+
+      -- ALGLIB --
+         Copyright 27.01.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void rmatrixsolvem(double[,] a, int n, double[,] b, int m, bool rfs, out int info, out densesolverreport rep, out double[,] x)
+    {
+        info = 0;
+        rep = new densesolverreport();
+        x = new double[0,0];
+        densesolver.rmatrixsolvem(a, n, b, m, rfs, ref info, rep.innerobj, ref x);
+        return;
+    }
+
+    /*************************************************************************
+    Dense solver.
+
+    This  subroutine  solves  a  system  A*X=B,  where A is NxN non-denegerate
+    real matrix given by its LU decomposition, X and B are NxM real matrices.
+
+    Algorithm features:
+    * automatic detection of degenerate cases
+    * O(N^2) complexity
+    * condition number estimation
+
+    No iterative refinement  is provided because exact form of original matrix
+    is not known to subroutine. Use RMatrixSolve or RMatrixMixedSolve  if  you
+    need iterative refinement.
+
+    INPUT PARAMETERS
+        LUA     -   array[0..N-1,0..N-1], LU decomposition, RMatrixLU result
+        P       -   array[0..N-1], pivots array, RMatrixLU result
+        N       -   size of A
+        B       -   array[0..N-1], right part
+
+    OUTPUT PARAMETERS
+        Info    -   same as in RMatrixSolve
+        Rep     -   same as in RMatrixSolve
+        X       -   same as in RMatrixSolve
+
+      -- ALGLIB --
+         Copyright 27.01.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void rmatrixlusolve(double[,] lua, int[] p, int n, double[] b, out int info, out densesolverreport rep, out double[] x)
+    {
+        info = 0;
+        rep = new densesolverreport();
+        x = new double[0];
+        densesolver.rmatrixlusolve(lua, p, n, b, ref info, rep.innerobj, ref x);
+        return;
+    }
+
+    /*************************************************************************
+    Dense solver.
+
+    Similar to RMatrixLUSolve() but solves task with multiple right parts
+    (where b and x are NxM matrices).
+
+    Algorithm features:
+    * automatic detection of degenerate cases
+    * O(M*N^2) complexity
+    * condition number estimation
+
+    No iterative refinement  is provided because exact form of original matrix
+    is not known to subroutine. Use RMatrixSolve or RMatrixMixedSolve  if  you
+    need iterative refinement.
+
+    INPUT PARAMETERS
+        LUA     -   array[0..N-1,0..N-1], LU decomposition, RMatrixLU result
+        P       -   array[0..N-1], pivots array, RMatrixLU result
+        N       -   size of A
+        B       -   array[0..N-1,0..M-1], right part
+        M       -   right part size
+
+    OUTPUT PARAMETERS
+        Info    -   same as in RMatrixSolve
+        Rep     -   same as in RMatrixSolve
+        X       -   same as in RMatrixSolve
+
+      -- ALGLIB --
+         Copyright 27.01.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void rmatrixlusolvem(double[,] lua, int[] p, int n, double[,] b, int m, out int info, out densesolverreport rep, out double[,] x)
+    {
+        info = 0;
+        rep = new densesolverreport();
+        x = new double[0,0];
+        densesolver.rmatrixlusolvem(lua, p, n, b, m, ref info, rep.innerobj, ref x);
+        return;
+    }
+
+    /*************************************************************************
+    Dense solver.
+
+    This  subroutine  solves  a  system  A*x=b,  where BOTH ORIGINAL A AND ITS
+    LU DECOMPOSITION ARE KNOWN. You can use it if for some  reasons  you  have
+    both A and its LU decomposition.
+
+    Algorithm features:
+    * automatic detection of degenerate cases
+    * condition number estimation
+    * iterative refinement
+    * O(N^2) complexity
+
+    INPUT PARAMETERS
+        A       -   array[0..N-1,0..N-1], system matrix
+        LUA     -   array[0..N-1,0..N-1], LU decomposition, RMatrixLU result
+        P       -   array[0..N-1], pivots array, RMatrixLU result
+        N       -   size of A
+        B       -   array[0..N-1], right part
+
+    OUTPUT PARAMETERS
+        Info    -   same as in RMatrixSolveM
+        Rep     -   same as in RMatrixSolveM
+        X       -   same as in RMatrixSolveM
+
+      -- ALGLIB --
+         Copyright 27.01.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void rmatrixmixedsolve(double[,] a, double[,] lua, int[] p, int n, double[] b, out int info, out densesolverreport rep, out double[] x)
+    {
+        info = 0;
+        rep = new densesolverreport();
+        x = new double[0];
+        densesolver.rmatrixmixedsolve(a, lua, p, n, b, ref info, rep.innerobj, ref x);
+        return;
+    }
+
+    /*************************************************************************
+    Dense solver.
+
+    Similar to RMatrixMixedSolve() but  solves task with multiple right  parts
+    (where b and x are NxM matrices).
+
+    Algorithm features:
+    * automatic detection of degenerate cases
+    * condition number estimation
+    * iterative refinement
+    * O(M*N^2) complexity
+
+    INPUT PARAMETERS
+        A       -   array[0..N-1,0..N-1], system matrix
+        LUA     -   array[0..N-1,0..N-1], LU decomposition, RMatrixLU result
+        P       -   array[0..N-1], pivots array, RMatrixLU result
+        N       -   size of A
+        B       -   array[0..N-1,0..M-1], right part
+        M       -   right part size
+
+    OUTPUT PARAMETERS
+        Info    -   same as in RMatrixSolveM
+        Rep     -   same as in RMatrixSolveM
+        X       -   same as in RMatrixSolveM
+
+      -- ALGLIB --
+         Copyright 27.01.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void rmatrixmixedsolvem(double[,] a, double[,] lua, int[] p, int n, double[,] b, int m, out int info, out densesolverreport rep, out double[,] x)
+    {
+        info = 0;
+        rep = new densesolverreport();
+        x = new double[0,0];
+        densesolver.rmatrixmixedsolvem(a, lua, p, n, b, m, ref info, rep.innerobj, ref x);
+        return;
+    }
+
+    /*************************************************************************
+    Dense solver. Same as RMatrixSolveM(), but for complex matrices.
+
+    Algorithm features:
+    * automatic detection of degenerate cases
+    * condition number estimation
+    * iterative refinement
+    * O(N^3+M*N^2) complexity
+
+    INPUT PARAMETERS
+        A       -   array[0..N-1,0..N-1], system matrix
+        N       -   size of A
+        B       -   array[0..N-1,0..M-1], right part
+        M       -   right part size
+        RFS     -   iterative refinement switch:
+                    * True - refinement is used.
+                      Less performance, more precision.
+                    * False - refinement is not used.
+                      More performance, less precision.
+
+    OUTPUT PARAMETERS
+        Info    -   same as in RMatrixSolve
+        Rep     -   same as in RMatrixSolve
+        X       -   same as in RMatrixSolve
+
+      -- ALGLIB --
+         Copyright 27.01.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void cmatrixsolvem(complex[,] a, int n, complex[,] b, int m, bool rfs, out int info, out densesolverreport rep, out complex[,] x)
+    {
+        info = 0;
+        rep = new densesolverreport();
+        x = new complex[0,0];
+        densesolver.cmatrixsolvem(a, n, b, m, rfs, ref info, rep.innerobj, ref x);
+        return;
+    }
+
+    /*************************************************************************
+    Dense solver. Same as RMatrixSolve(), but for complex matrices.
+
+    Algorithm features:
+    * automatic detection of degenerate cases
+    * condition number estimation
+    * iterative refinement
+    * O(N^3) complexity
+
+    INPUT PARAMETERS
+        A       -   array[0..N-1,0..N-1], system matrix
+        N       -   size of A
+        B       -   array[0..N-1], right part
+
+    OUTPUT PARAMETERS
+        Info    -   same as in RMatrixSolve
+        Rep     -   same as in RMatrixSolve
+        X       -   same as in RMatrixSolve
+
+      -- ALGLIB --
+         Copyright 27.01.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void cmatrixsolve(complex[,] a, int n, complex[] b, out int info, out densesolverreport rep, out complex[] x)
+    {
+        info = 0;
+        rep = new densesolverreport();
+        x = new complex[0];
+        densesolver.cmatrixsolve(a, n, b, ref info, rep.innerobj, ref x);
+        return;
+    }
+
+    /*************************************************************************
+    Dense solver. Same as RMatrixLUSolveM(), but for complex matrices.
+
+    Algorithm features:
+    * automatic detection of degenerate cases
+    * O(M*N^2) complexity
+    * condition number estimation
+
+    No iterative refinement  is provided because exact form of original matrix
+    is not known to subroutine. Use CMatrixSolve or CMatrixMixedSolve  if  you
+    need iterative refinement.
+
+    INPUT PARAMETERS
+        LUA     -   array[0..N-1,0..N-1], LU decomposition, RMatrixLU result
+        P       -   array[0..N-1], pivots array, RMatrixLU result
+        N       -   size of A
+        B       -   array[0..N-1,0..M-1], right part
+        M       -   right part size
+
+    OUTPUT PARAMETERS
+        Info    -   same as in RMatrixSolve
+        Rep     -   same as in RMatrixSolve
+        X       -   same as in RMatrixSolve
+
+      -- ALGLIB --
+         Copyright 27.01.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void cmatrixlusolvem(complex[,] lua, int[] p, int n, complex[,] b, int m, out int info, out densesolverreport rep, out complex[,] x)
+    {
+        info = 0;
+        rep = new densesolverreport();
+        x = new complex[0,0];
+        densesolver.cmatrixlusolvem(lua, p, n, b, m, ref info, rep.innerobj, ref x);
+        return;
+    }
+
+    /*************************************************************************
+    Dense solver. Same as RMatrixLUSolve(), but for complex matrices.
+
+    Algorithm features:
+    * automatic detection of degenerate cases
+    * O(N^2) complexity
+    * condition number estimation
+
+    No iterative refinement is provided because exact form of original matrix
+    is not known to subroutine. Use CMatrixSolve or CMatrixMixedSolve  if  you
+    need iterative refinement.
+
+    INPUT PARAMETERS
+        LUA     -   array[0..N-1,0..N-1], LU decomposition, CMatrixLU result
+        P       -   array[0..N-1], pivots array, CMatrixLU result
+        N       -   size of A
+        B       -   array[0..N-1], right part
+
+    OUTPUT PARAMETERS
+        Info    -   same as in RMatrixSolve
+        Rep     -   same as in RMatrixSolve
+        X       -   same as in RMatrixSolve
+
+      -- ALGLIB --
+         Copyright 27.01.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void cmatrixlusolve(complex[,] lua, int[] p, int n, complex[] b, out int info, out densesolverreport rep, out complex[] x)
+    {
+        info = 0;
+        rep = new densesolverreport();
+        x = new complex[0];
+        densesolver.cmatrixlusolve(lua, p, n, b, ref info, rep.innerobj, ref x);
+        return;
+    }
+
+    /*************************************************************************
+    Dense solver. Same as RMatrixMixedSolveM(), but for complex matrices.
+
+    Algorithm features:
+    * automatic detection of degenerate cases
+    * condition number estimation
+    * iterative refinement
+    * O(M*N^2) complexity
+
+    INPUT PARAMETERS
+        A       -   array[0..N-1,0..N-1], system matrix
+        LUA     -   array[0..N-1,0..N-1], LU decomposition, CMatrixLU result
+        P       -   array[0..N-1], pivots array, CMatrixLU result
+        N       -   size of A
+        B       -   array[0..N-1,0..M-1], right part
+        M       -   right part size
+
+    OUTPUT PARAMETERS
+        Info    -   same as in RMatrixSolveM
+        Rep     -   same as in RMatrixSolveM
+        X       -   same as in RMatrixSolveM
+
+      -- ALGLIB --
+         Copyright 27.01.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void cmatrixmixedsolvem(complex[,] a, complex[,] lua, int[] p, int n, complex[,] b, int m, out int info, out densesolverreport rep, out complex[,] x)
+    {
+        info = 0;
+        rep = new densesolverreport();
+        x = new complex[0,0];
+        densesolver.cmatrixmixedsolvem(a, lua, p, n, b, m, ref info, rep.innerobj, ref x);
+        return;
+    }
+
+    /*************************************************************************
+    Dense solver. Same as RMatrixMixedSolve(), but for complex matrices.
+
+    Algorithm features:
+    * automatic detection of degenerate cases
+    * condition number estimation
+    * iterative refinement
+    * O(N^2) complexity
+
+    INPUT PARAMETERS
+        A       -   array[0..N-1,0..N-1], system matrix
+        LUA     -   array[0..N-1,0..N-1], LU decomposition, CMatrixLU result
+        P       -   array[0..N-1], pivots array, CMatrixLU result
+        N       -   size of A
+        B       -   array[0..N-1], right part
+
+    OUTPUT PARAMETERS
+        Info    -   same as in RMatrixSolveM
+        Rep     -   same as in RMatrixSolveM
+        X       -   same as in RMatrixSolveM
+
+      -- ALGLIB --
+         Copyright 27.01.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void cmatrixmixedsolve(complex[,] a, complex[,] lua, int[] p, int n, complex[] b, out int info, out densesolverreport rep, out complex[] x)
+    {
+        info = 0;
+        rep = new densesolverreport();
+        x = new complex[0];
+        densesolver.cmatrixmixedsolve(a, lua, p, n, b, ref info, rep.innerobj, ref x);
+        return;
+    }
+
+    /*************************************************************************
+    Dense solver. Same as RMatrixSolveM(), but for symmetric positive definite
+    matrices.
+
+    Algorithm features:
+    * automatic detection of degenerate cases
+    * condition number estimation
+    * O(N^3+M*N^2) complexity
+    * matrix is represented by its upper or lower triangle
+
+    No iterative refinement is provided because such partial representation of
+    matrix does not allow efficient calculation of extra-precise  matrix-vector
+    products for large matrices. Use RMatrixSolve or RMatrixMixedSolve  if  you
+    need iterative refinement.
+
+    INPUT PARAMETERS
+        A       -   array[0..N-1,0..N-1], system matrix
+        N       -   size of A
+        IsUpper -   what half of A is provided
+        B       -   array[0..N-1,0..M-1], right part
+        M       -   right part size
+
+    OUTPUT PARAMETERS
+        Info    -   same as in RMatrixSolve.
+                    Returns -3 for non-SPD matrices.
+        Rep     -   same as in RMatrixSolve
+        X       -   same as in RMatrixSolve
+
+      -- ALGLIB --
+         Copyright 27.01.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void spdmatrixsolvem(double[,] a, int n, bool isupper, double[,] b, int m, out int info, out densesolverreport rep, out double[,] x)
+    {
+        info = 0;
+        rep = new densesolverreport();
+        x = new double[0,0];
+        densesolver.spdmatrixsolvem(a, n, isupper, b, m, ref info, rep.innerobj, ref x);
+        return;
+    }
+
+    /*************************************************************************
+    Dense solver. Same as RMatrixSolve(), but for SPD matrices.
+
+    Algorithm features:
+    * automatic detection of degenerate cases
+    * condition number estimation
+    * O(N^3) complexity
+    * matrix is represented by its upper or lower triangle
+
+    No iterative refinement is provided because such partial representation of
+    matrix does not allow efficient calculation of extra-precise  matrix-vector
+    products for large matrices. Use RMatrixSolve or RMatrixMixedSolve  if  you
+    need iterative refinement.
+
+    INPUT PARAMETERS
+        A       -   array[0..N-1,0..N-1], system matrix
+        N       -   size of A
+        IsUpper -   what half of A is provided
+        B       -   array[0..N-1], right part
+
+    OUTPUT PARAMETERS
+        Info    -   same as in RMatrixSolve
+                    Returns -3 for non-SPD matrices.
+        Rep     -   same as in RMatrixSolve
+        X       -   same as in RMatrixSolve
+
+      -- ALGLIB --
+         Copyright 27.01.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void spdmatrixsolve(double[,] a, int n, bool isupper, double[] b, out int info, out densesolverreport rep, out double[] x)
+    {
+        info = 0;
+        rep = new densesolverreport();
+        x = new double[0];
+        densesolver.spdmatrixsolve(a, n, isupper, b, ref info, rep.innerobj, ref x);
+        return;
+    }
+
+    /*************************************************************************
+    Dense solver. Same as RMatrixLUSolveM(), but for SPD matrices  represented
+    by their Cholesky decomposition.
+
+    Algorithm features:
+    * automatic detection of degenerate cases
+    * O(M*N^2) complexity
+    * condition number estimation
+    * matrix is represented by its upper or lower triangle
+
+    No iterative refinement is provided because such partial representation of
+    matrix does not allow efficient calculation of extra-precise  matrix-vector
+    products for large matrices. Use RMatrixSolve or RMatrixMixedSolve  if  you
+    need iterative refinement.
+
+    INPUT PARAMETERS
+        CHA     -   array[0..N-1,0..N-1], Cholesky decomposition,
+                    SPDMatrixCholesky result
+        N       -   size of CHA
+        IsUpper -   what half of CHA is provided
+        B       -   array[0..N-1,0..M-1], right part
+        M       -   right part size
+
+    OUTPUT PARAMETERS
+        Info    -   same as in RMatrixSolve
+        Rep     -   same as in RMatrixSolve
+        X       -   same as in RMatrixSolve
+
+      -- ALGLIB --
+         Copyright 27.01.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void spdmatrixcholeskysolvem(double[,] cha, int n, bool isupper, double[,] b, int m, out int info, out densesolverreport rep, out double[,] x)
+    {
+        info = 0;
+        rep = new densesolverreport();
+        x = new double[0,0];
+        densesolver.spdmatrixcholeskysolvem(cha, n, isupper, b, m, ref info, rep.innerobj, ref x);
+        return;
+    }
+
+    /*************************************************************************
+    Dense solver. Same as RMatrixLUSolve(), but for  SPD matrices  represented
+    by their Cholesky decomposition.
+
+    Algorithm features:
+    * automatic detection of degenerate cases
+    * O(N^2) complexity
+    * condition number estimation
+    * matrix is represented by its upper or lower triangle
+
+    No iterative refinement is provided because such partial representation of
+    matrix does not allow efficient calculation of extra-precise  matrix-vector
+    products for large matrices. Use RMatrixSolve or RMatrixMixedSolve  if  you
+    need iterative refinement.
+
+    INPUT PARAMETERS
+        CHA     -   array[0..N-1,0..N-1], Cholesky decomposition,
+                    SPDMatrixCholesky result
+        N       -   size of A
+        IsUpper -   what half of CHA is provided
+        B       -   array[0..N-1], right part
+
+    OUTPUT PARAMETERS
+        Info    -   same as in RMatrixSolve
+        Rep     -   same as in RMatrixSolve
+        X       -   same as in RMatrixSolve
+
+      -- ALGLIB --
+         Copyright 27.01.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void spdmatrixcholeskysolve(double[,] cha, int n, bool isupper, double[] b, out int info, out densesolverreport rep, out double[] x)
+    {
+        info = 0;
+        rep = new densesolverreport();
+        x = new double[0];
+        densesolver.spdmatrixcholeskysolve(cha, n, isupper, b, ref info, rep.innerobj, ref x);
+        return;
+    }
+
+    /*************************************************************************
+    Dense solver. Same as RMatrixSolveM(), but for Hermitian positive definite
+    matrices.
+
+    Algorithm features:
+    * automatic detection of degenerate cases
+    * condition number estimation
+    * O(N^3+M*N^2) complexity
+    * matrix is represented by its upper or lower triangle
+
+    No iterative refinement is provided because such partial representation of
+    matrix does not allow efficient calculation of extra-precise  matrix-vector
+    products for large matrices. Use RMatrixSolve or RMatrixMixedSolve  if  you
+    need iterative refinement.
+
+    INPUT PARAMETERS
+        A       -   array[0..N-1,0..N-1], system matrix
+        N       -   size of A
+        IsUpper -   what half of A is provided
+        B       -   array[0..N-1,0..M-1], right part
+        M       -   right part size
+
+    OUTPUT PARAMETERS
+        Info    -   same as in RMatrixSolve.
+                    Returns -3 for non-HPD matrices.
+        Rep     -   same as in RMatrixSolve
+        X       -   same as in RMatrixSolve
+
+      -- ALGLIB --
+         Copyright 27.01.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void hpdmatrixsolvem(complex[,] a, int n, bool isupper, complex[,] b, int m, out int info, out densesolverreport rep, out complex[,] x)
+    {
+        info = 0;
+        rep = new densesolverreport();
+        x = new complex[0,0];
+        densesolver.hpdmatrixsolvem(a, n, isupper, b, m, ref info, rep.innerobj, ref x);
+        return;
+    }
+
+    /*************************************************************************
+    Dense solver. Same as RMatrixSolve(),  but for Hermitian positive definite
+    matrices.
+
+    Algorithm features:
+    * automatic detection of degenerate cases
+    * condition number estimation
+    * O(N^3) complexity
+    * matrix is represented by its upper or lower triangle
+
+    No iterative refinement is provided because such partial representation of
+    matrix does not allow efficient calculation of extra-precise  matrix-vector
+    products for large matrices. Use RMatrixSolve or RMatrixMixedSolve  if  you
+    need iterative refinement.
+
+    INPUT PARAMETERS
+        A       -   array[0..N-1,0..N-1], system matrix
+        N       -   size of A
+        IsUpper -   what half of A is provided
+        B       -   array[0..N-1], right part
+
+    OUTPUT PARAMETERS
+        Info    -   same as in RMatrixSolve
+                    Returns -3 for non-HPD matrices.
+        Rep     -   same as in RMatrixSolve
+        X       -   same as in RMatrixSolve
+
+      -- ALGLIB --
+         Copyright 27.01.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void hpdmatrixsolve(complex[,] a, int n, bool isupper, complex[] b, out int info, out densesolverreport rep, out complex[] x)
+    {
+        info = 0;
+        rep = new densesolverreport();
+        x = new complex[0];
+        densesolver.hpdmatrixsolve(a, n, isupper, b, ref info, rep.innerobj, ref x);
+        return;
+    }
+
+    /*************************************************************************
+    Dense solver. Same as RMatrixLUSolveM(), but for HPD matrices  represented
+    by their Cholesky decomposition.
+
+    Algorithm features:
+    * automatic detection of degenerate cases
+    * O(M*N^2) complexity
+    * condition number estimation
+    * matrix is represented by its upper or lower triangle
+
+    No iterative refinement is provided because such partial representation of
+    matrix does not allow efficient calculation of extra-precise  matrix-vector
+    products for large matrices. Use RMatrixSolve or RMatrixMixedSolve  if  you
+    need iterative refinement.
+
+    INPUT PARAMETERS
+        CHA     -   array[0..N-1,0..N-1], Cholesky decomposition,
+                    HPDMatrixCholesky result
+        N       -   size of CHA
+        IsUpper -   what half of CHA is provided
+        B       -   array[0..N-1,0..M-1], right part
+        M       -   right part size
+
+    OUTPUT PARAMETERS
+        Info    -   same as in RMatrixSolve
+        Rep     -   same as in RMatrixSolve
+        X       -   same as in RMatrixSolve
+
+      -- ALGLIB --
+         Copyright 27.01.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void hpdmatrixcholeskysolvem(complex[,] cha, int n, bool isupper, complex[,] b, int m, out int info, out densesolverreport rep, out complex[,] x)
+    {
+        info = 0;
+        rep = new densesolverreport();
+        x = new complex[0,0];
+        densesolver.hpdmatrixcholeskysolvem(cha, n, isupper, b, m, ref info, rep.innerobj, ref x);
+        return;
+    }
+
+    /*************************************************************************
+    Dense solver. Same as RMatrixLUSolve(), but for  HPD matrices  represented
+    by their Cholesky decomposition.
+
+    Algorithm features:
+    * automatic detection of degenerate cases
+    * O(N^2) complexity
+    * condition number estimation
+    * matrix is represented by its upper or lower triangle
+
+    No iterative refinement is provided because such partial representation of
+    matrix does not allow efficient calculation of extra-precise  matrix-vector
+    products for large matrices. Use RMatrixSolve or RMatrixMixedSolve  if  you
+    need iterative refinement.
+
+    INPUT PARAMETERS
+        CHA     -   array[0..N-1,0..N-1], Cholesky decomposition,
+                    SPDMatrixCholesky result
+        N       -   size of A
+        IsUpper -   what half of CHA is provided
+        B       -   array[0..N-1], right part
+
+    OUTPUT PARAMETERS
+        Info    -   same as in RMatrixSolve
+        Rep     -   same as in RMatrixSolve
+        X       -   same as in RMatrixSolve
+
+      -- ALGLIB --
+         Copyright 27.01.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void hpdmatrixcholeskysolve(complex[,] cha, int n, bool isupper, complex[] b, out int info, out densesolverreport rep, out complex[] x)
+    {
+        info = 0;
+        rep = new densesolverreport();
+        x = new complex[0];
+        densesolver.hpdmatrixcholeskysolve(cha, n, isupper, b, ref info, rep.innerobj, ref x);
+        return;
+    }
+
+    /*************************************************************************
+    Dense solver.
+
+    This subroutine finds solution of the linear system A*X=B with non-square,
+    possibly degenerate A.  System  is  solved in the least squares sense, and
+    general least squares solution  X = X0 + CX*y  which  minimizes |A*X-B| is
+    returned. If A is non-degenerate, solution in the  usual sense is returned
+
+    Algorithm features:
+    * automatic detection of degenerate cases
+    * iterative refinement
+    * O(N^3) complexity
+
+    INPUT PARAMETERS
+        A       -   array[0..NRows-1,0..NCols-1], system matrix
+        NRows   -   vertical size of A
+        NCols   -   horizontal size of A
+        B       -   array[0..NCols-1], right part
+        Threshold-  a number in [0,1]. Singular values  beyond  Threshold  are
+                    considered  zero.  Set  it to 0.0, if you don't understand
+                    what it means, so the solver will choose good value on its
+                    own.
+
+    OUTPUT PARAMETERS
+        Info    -   return code:
+                    * -4    SVD subroutine failed
+                    * -1    if NRows<=0 or NCols<=0 or Threshold<0 was passed
+                    *  1    if task is solved
+        Rep     -   solver report, see below for more info
+        X       -   array[0..N-1,0..M-1], it contains:
+                    * solution of A*X=B if A is non-singular (well-conditioned
+                      or ill-conditioned, but not very close to singular)
+                    * zeros,  if  A  is  singular  or  VERY  close to singular
+                      (in this case Info=-3).
+
+    SOLVER REPORT
+
+    Subroutine sets following fields of the Rep structure:
+    * R2        reciprocal of condition number: 1/cond(A), 2-norm.
+    * N         = NCols
+    * K         dim(Null(A))
+    * CX        array[0..N-1,0..K-1], kernel of A.
+                Columns of CX store such vectors that A*CX[i]=0.
+
+      -- ALGLIB --
+         Copyright 24.08.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void rmatrixsolvels(double[,] a, int nrows, int ncols, double[] b, double threshold, out int info, out densesolverlsreport rep, out double[] x)
+    {
+        info = 0;
+        rep = new densesolverlsreport();
+        x = new double[0];
+        densesolver.rmatrixsolvels(a, nrows, ncols, b, threshold, ref info, rep.innerobj, ref x);
+        return;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    This object stores state of the LinLSQR method.
+
+    You should use ALGLIB functions to work with this object.
+    *************************************************************************/
+    public class linlsqrstate
+    {
+        //
+        // Public declarations
+        //
+
+        public linlsqrstate()
+        {
+            _innerobj = new linlsqr.linlsqrstate();
+        }
+
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private linlsqr.linlsqrstate _innerobj;
+        public linlsqr.linlsqrstate innerobj { get { return _innerobj; } }
+        public linlsqrstate(linlsqr.linlsqrstate obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+
+    /*************************************************************************
+
+    *************************************************************************/
+    public class linlsqrreport
+    {
+        //
+        // Public declarations
+        //
+        public int iterationscount { get { return _innerobj.iterationscount; } set { _innerobj.iterationscount = value; } }
+        public int nmv { get { return _innerobj.nmv; } set { _innerobj.nmv = value; } }
+        public int terminationtype { get { return _innerobj.terminationtype; } set { _innerobj.terminationtype = value; } }
+
+        public linlsqrreport()
+        {
+            _innerobj = new linlsqr.linlsqrreport();
+        }
+
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private linlsqr.linlsqrreport _innerobj;
+        public linlsqr.linlsqrreport innerobj { get { return _innerobj; } }
+        public linlsqrreport(linlsqr.linlsqrreport obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+    /*************************************************************************
+    This function initializes linear LSQR Solver. This solver is used to solve
+    non-symmetric (and, possibly, non-square) problems. Least squares solution
+    is returned for non-compatible systems.
+
+    USAGE:
+    1. User initializes algorithm state with LinLSQRCreate() call
+    2. User tunes solver parameters with  LinLSQRSetCond() and other functions
+    3. User  calls  LinLSQRSolveSparse()  function which takes algorithm state
+       and SparseMatrix object.
+    4. User calls LinLSQRResults() to get solution
+    5. Optionally, user may call LinLSQRSolveSparse() again to  solve  another
+       problem  with different matrix and/or right part without reinitializing
+       LinLSQRState structure.
+
+    INPUT PARAMETERS:
+        M       -   number of rows in A
+        N       -   number of variables, N>0
+
+    OUTPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+
+      -- ALGLIB --
+         Copyright 30.11.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void linlsqrcreate(int m, int n, out linlsqrstate state)
+    {
+        state = new linlsqrstate();
+        linlsqr.linlsqrcreate(m, n, state.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    This function sets optional Tikhonov regularization coefficient.
+    It is zero by default.
+
+    INPUT PARAMETERS:
+        LambdaI -   regularization factor, LambdaI>=0
+
+    OUTPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+
+      -- ALGLIB --
+         Copyright 30.11.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void linlsqrsetlambdai(linlsqrstate state, double lambdai)
+    {
+
+        linlsqr.linlsqrsetlambdai(state.innerobj, lambdai);
+        return;
+    }
+
+    /*************************************************************************
+    Procedure for solution of A*x=b with sparse A.
+
+    INPUT PARAMETERS:
+        State   -   algorithm state
+        A       -   sparse M*N matrix in the CRS format (you MUST contvert  it
+                    to CRS format  by  calling  SparseConvertToCRS()  function
+                    BEFORE you pass it to this function).
+        B       -   right part, array[M]
+
+    RESULT:
+        This function returns no result.
+        You can get solution by calling LinCGResults()
+
+      -- ALGLIB --
+         Copyright 30.11.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void linlsqrsolvesparse(linlsqrstate state, sparsematrix a, double[] b)
+    {
+
+        linlsqr.linlsqrsolvesparse(state.innerobj, a.innerobj, b);
+        return;
+    }
+
+    /*************************************************************************
+    This function sets stopping criteria.
+
+    INPUT PARAMETERS:
+        EpsA    -   algorithm will be stopped if ||A^T*Rk||/(||A||*||Rk||)<=EpsA.
+        EpsB    -   algorithm will be stopped if ||Rk||<=EpsB*||B||
+        MaxIts  -   algorithm will be stopped if number of iterations
+                    more than MaxIts.
+
+    OUTPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+
+    NOTE: if EpsA,EpsB,EpsC and MaxIts are zero then these variables will
+    be setted as default values.
+
+      -- ALGLIB --
+         Copyright 30.11.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void linlsqrsetcond(linlsqrstate state, double epsa, double epsb, int maxits)
+    {
+
+        linlsqr.linlsqrsetcond(state.innerobj, epsa, epsb, maxits);
+        return;
+    }
+
+    /*************************************************************************
+    LSQR solver: results.
+
+    This function must be called after LinLSQRSolve
+
+    INPUT PARAMETERS:
+        State   -   algorithm state
+
+    OUTPUT PARAMETERS:
+        X       -   array[N], solution
+        Rep     -   optimization report:
+                    * Rep.TerminationType completetion code:
+                        *  1    ||Rk||<=EpsB*||B||
+                        *  4    ||A^T*Rk||/(||A||*||Rk||)<=EpsA
+                        *  5    MaxIts steps was taken
+                        *  7    rounding errors prevent further progress,
+                                X contains best point found so far.
+                                (sometimes returned on singular systems)
+                    * Rep.IterationsCount contains iterations count
+                    * NMV countains number of matrix-vector calculations
+
+      -- ALGLIB --
+         Copyright 30.11.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void linlsqrresults(linlsqrstate state, out double[] x, out linlsqrreport rep)
+    {
+        x = new double[0];
+        rep = new linlsqrreport();
+        linlsqr.linlsqrresults(state.innerobj, ref x, rep.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    This function turns on/off reporting.
+
+    INPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+        NeedXRep-   whether iteration reports are needed or not
+
+    If NeedXRep is True, algorithm will call rep() callback function if  it is
+    provided to MinCGOptimize().
+
+      -- ALGLIB --
+         Copyright 30.11.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void linlsqrsetxrep(linlsqrstate state, bool needxrep)
+    {
+
+        linlsqr.linlsqrsetxrep(state.innerobj, needxrep);
+        return;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    This object stores state of the linear CG method.
+
+    You should use ALGLIB functions to work with this object.
+    Never try to access its fields directly!
+    *************************************************************************/
+    public class lincgstate
+    {
+        //
+        // Public declarations
+        //
+
+        public lincgstate()
+        {
+            _innerobj = new lincg.lincgstate();
+        }
+
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private lincg.lincgstate _innerobj;
+        public lincg.lincgstate innerobj { get { return _innerobj; } }
+        public lincgstate(lincg.lincgstate obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+
+    /*************************************************************************
+
+    *************************************************************************/
+    public class lincgreport
+    {
+        //
+        // Public declarations
+        //
+        public int iterationscount { get { return _innerobj.iterationscount; } set { _innerobj.iterationscount = value; } }
+        public int nmv { get { return _innerobj.nmv; } set { _innerobj.nmv = value; } }
+        public int terminationtype { get { return _innerobj.terminationtype; } set { _innerobj.terminationtype = value; } }
+        public double r2 { get { return _innerobj.r2; } set { _innerobj.r2 = value; } }
+
+        public lincgreport()
+        {
+            _innerobj = new lincg.lincgreport();
+        }
+
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private lincg.lincgreport _innerobj;
+        public lincg.lincgreport innerobj { get { return _innerobj; } }
+        public lincgreport(lincg.lincgreport obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+    /*************************************************************************
+    This function initializes linear CG Solver. This solver is used  to  solve
+    symmetric positive definite problems. If you want  to  solve  nonsymmetric
+    (or non-positive definite) problem you may use LinLSQR solver provided  by
+    ALGLIB.
+
+    USAGE:
+    1. User initializes algorithm state with LinCGCreate() call
+    2. User tunes solver parameters with  LinCGSetCond() and other functions
+    3. Optionally, user sets starting point with LinCGSetStartingPoint()
+    4. User  calls LinCGSolveSparse() function which takes algorithm state and
+       SparseMatrix object.
+    5. User calls LinCGResults() to get solution
+    6. Optionally, user may call LinCGSolveSparse()  again  to  solve  another
+       problem  with different matrix and/or right part without reinitializing
+       LinCGState structure.
+
+    INPUT PARAMETERS:
+        N       -   problem dimension, N>0
+
+    OUTPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+
+      -- ALGLIB --
+         Copyright 14.11.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void lincgcreate(int n, out lincgstate state)
+    {
+        state = new lincgstate();
+        lincg.lincgcreate(n, state.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    This function sets starting point.
+    By default, zero starting point is used.
+
+    INPUT PARAMETERS:
+        X       -   starting point, array[N]
+
+    OUTPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+
+      -- ALGLIB --
+         Copyright 14.11.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void lincgsetstartingpoint(lincgstate state, double[] x)
+    {
+
+        lincg.lincgsetstartingpoint(state.innerobj, x);
+        return;
+    }
+
+    /*************************************************************************
+    This function sets stopping criteria.
+
+    INPUT PARAMETERS:
+        EpsF    -   algorithm will be stopped if norm of residual is less than
+                    EpsF*||b||.
+        MaxIts  -   algorithm will be stopped if number of iterations is  more
+                    than MaxIts.
+
+    OUTPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+
+    NOTES:
+    If  both  EpsF  and  MaxIts  are  zero then small EpsF will be set to small
+    value.
+
+      -- ALGLIB --
+         Copyright 14.11.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void lincgsetcond(lincgstate state, double epsf, int maxits)
+    {
+
+        lincg.lincgsetcond(state.innerobj, epsf, maxits);
+        return;
+    }
+
+    /*************************************************************************
+    Procedure for solution of A*x=b with sparse A.
+
+    INPUT PARAMETERS:
+        State   -   algorithm state
+        A       -   sparse matrix in the CRS format (you MUST contvert  it  to
+                    CRS format by calling SparseConvertToCRS() function).
+        IsUpper -   whether upper or lower triangle of A is used:
+                    * IsUpper=True  => only upper triangle is used and lower
+                                       triangle is not referenced at all
+                    * IsUpper=False => only lower triangle is used and upper
+                                       triangle is not referenced at all
+        B       -   right part, array[N]
+
+    RESULT:
+        This function returns no result.
+        You can get solution by calling LinCGResults()
+
+      -- ALGLIB --
+         Copyright 14.11.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void lincgsolvesparse(lincgstate state, sparsematrix a, bool isupper, double[] b)
+    {
+
+        lincg.lincgsolvesparse(state.innerobj, a.innerobj, isupper, b);
+        return;
+    }
+
+    /*************************************************************************
+    CG-solver: results.
+
+    This function must be called after LinCGSolve
+
+    INPUT PARAMETERS:
+        State   -   algorithm state
+
+    OUTPUT PARAMETERS:
+        X       -   array[N], solution
+        Rep     -   optimization report:
+                    * Rep.TerminationType completetion code:
+                        * -5    input matrix is either not positive definite,
+                                too large or too small
+                        * -4    overflow/underflow during solution
+                                (ill conditioned problem)
+                        *  1    ||residual||<=EpsF*||b||
+                        *  5    MaxIts steps was taken
+                        *  7    rounding errors prevent further progress,
+                                best point found is returned
+                    * Rep.IterationsCount contains iterations count
+                    * NMV countains number of matrix-vector calculations
+
+      -- ALGLIB --
+         Copyright 14.11.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void lincgresults(lincgstate state, out double[] x, out lincgreport rep)
+    {
+        x = new double[0];
+        rep = new lincgreport();
+        lincg.lincgresults(state.innerobj, ref x, rep.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    This function sets restart frequency. By default, algorithm  is  restarted
+    after N subsequent iterations.
+
+      -- ALGLIB --
+         Copyright 14.11.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void lincgsetrestartfreq(lincgstate state, int srf)
+    {
+
+        lincg.lincgsetrestartfreq(state.innerobj, srf);
+        return;
+    }
+
+    /*************************************************************************
+    This function sets frequency of residual recalculations.
+
+    Algorithm updates residual r_k using iterative formula,  but  recalculates
+    it from scratch after each 10 iterations. It is done to avoid accumulation
+    of numerical errors and to stop algorithm when r_k starts to grow.
+
+    Such low update frequence (1/10) gives very  little  overhead,  but  makes
+    algorithm a bit more robust against numerical errors. However, you may
+    change it
+
+    INPUT PARAMETERS:
+        Freq    -   desired update frequency, Freq>=0.
+                    Zero value means that no updates will be done.
+
+      -- ALGLIB --
+         Copyright 14.11.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void lincgsetrupdatefreq(lincgstate state, int freq)
+    {
+
+        lincg.lincgsetrupdatefreq(state.innerobj, freq);
+        return;
+    }
+
+    /*************************************************************************
+    This function turns on/off reporting.
+
+    INPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+        NeedXRep-   whether iteration reports are needed or not
+
+    If NeedXRep is True, algorithm will call rep() callback function if  it is
+    provided to MinCGOptimize().
+
+      -- ALGLIB --
+         Copyright 14.11.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void lincgsetxrep(lincgstate state, bool needxrep)
+    {
+
+        lincg.lincgsetxrep(state.innerobj, needxrep);
+        return;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+
+    *************************************************************************/
+    public class nleqstate
+    {
+        //
+        // Public declarations
+        //
+        public bool needf { get { return _innerobj.needf; } set { _innerobj.needf = value; } }
+        public bool needfij { get { return _innerobj.needfij; } set { _innerobj.needfij = value; } }
+        public bool xupdated { get { return _innerobj.xupdated; } set { _innerobj.xupdated = value; } }
+        public double f { get { return _innerobj.f; } set { _innerobj.f = value; } }
+        public double[] fi { get { return _innerobj.fi; } }
+        public double[,] j { get { return _innerobj.j; } }
+        public double[] x { get { return _innerobj.x; } }
+
+        public nleqstate()
+        {
+            _innerobj = new nleq.nleqstate();
+        }
+
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private nleq.nleqstate _innerobj;
+        public nleq.nleqstate innerobj { get { return _innerobj; } }
+        public nleqstate(nleq.nleqstate obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+
+    /*************************************************************************
+
+    *************************************************************************/
+    public class nleqreport
+    {
+        //
+        // Public declarations
+        //
+        public int iterationscount { get { return _innerobj.iterationscount; } set { _innerobj.iterationscount = value; } }
+        public int nfunc { get { return _innerobj.nfunc; } set { _innerobj.nfunc = value; } }
+        public int njac { get { return _innerobj.njac; } set { _innerobj.njac = value; } }
+        public int terminationtype { get { return _innerobj.terminationtype; } set { _innerobj.terminationtype = value; } }
+
+        public nleqreport()
+        {
+            _innerobj = new nleq.nleqreport();
+        }
+
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private nleq.nleqreport _innerobj;
+        public nleq.nleqreport innerobj { get { return _innerobj; } }
+        public nleqreport(nleq.nleqreport obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+    /*************************************************************************
+                    LEVENBERG-MARQUARDT-LIKE NONLINEAR SOLVER
+
+    DESCRIPTION:
+    This algorithm solves system of nonlinear equations
+        F[0](x[0], ..., x[n-1])   = 0
+        F[1](x[0], ..., x[n-1])   = 0
+        ...
+        F[M-1](x[0], ..., x[n-1]) = 0
+    with M/N do not necessarily coincide.  Algorithm  converges  quadratically
+    under following conditions:
+        * the solution set XS is nonempty
+        * for some xs in XS there exist such neighbourhood N(xs) that:
+          * vector function F(x) and its Jacobian J(x) are continuously
+            differentiable on N
+          * ||F(x)|| provides local error bound on N, i.e. there  exists  such
+            c1, that ||F(x)||>c1*distance(x,XS)
+    Note that these conditions are much more weaker than usual non-singularity
+    conditions. For example, algorithm will converge for any  affine  function
+    F (whether its Jacobian singular or not).
+
+
+    REQUIREMENTS:
+    Algorithm will request following information during its operation:
+    * function vector F[] and Jacobian matrix at given point X
+    * value of merit function f(x)=F[0]^2(x)+...+F[M-1]^2(x) at given point X
+
+
+    USAGE:
+    1. User initializes algorithm state with NLEQCreateLM() call
+    2. User tunes solver parameters with  NLEQSetCond(),  NLEQSetStpMax()  and
+       other functions
+    3. User  calls  NLEQSolve()  function  which  takes  algorithm  state  and
+       pointers (delegates, etc.) to callback functions which calculate  merit
+       function value and Jacobian.
+    4. User calls NLEQResults() to get solution
+    5. Optionally, user may call NLEQRestartFrom() to  solve  another  problem
+       with same parameters (N/M) but another starting  point  and/or  another
+       function vector. NLEQRestartFrom() allows to reuse already  initialized
+       structure.
+
+
+    INPUT PARAMETERS:
+        N       -   space dimension, N>1:
+                    * if provided, only leading N elements of X are used
+                    * if not provided, determined automatically from size of X
+        M       -   system size
+        X       -   starting point
+
+
+    OUTPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+
+
+    NOTES:
+    1. you may tune stopping conditions with NLEQSetCond() function
+    2. if target function contains exp() or other fast growing functions,  and
+       optimization algorithm makes too large steps which leads  to  overflow,
+       use NLEQSetStpMax() function to bound algorithm's steps.
+    3. this  algorithm  is  a  slightly  modified implementation of the method
+       described  in  'Levenberg-Marquardt  method  for constrained  nonlinear
+       equations with strong local convergence properties' by Christian Kanzow
+       Nobuo Yamashita and Masao Fukushima and further  developed  in  'On the
+       convergence of a New Levenberg-Marquardt Method'  by  Jin-yan  Fan  and
+       Ya-Xiang Yuan.
+
+
+      -- ALGLIB --
+         Copyright 20.08.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void nleqcreatelm(int n, int m, double[] x, out nleqstate state)
+    {
+        state = new nleqstate();
+        nleq.nleqcreatelm(n, m, x, state.innerobj);
+        return;
+    }
+    public static void nleqcreatelm(int m, double[] x, out nleqstate state)
+    {
+        int n;
+
+        state = new nleqstate();
+        n = ap.len(x);
+        nleq.nleqcreatelm(n, m, x, state.innerobj);
+
+        return;
+    }
+
+    /*************************************************************************
+    This function sets stopping conditions for the nonlinear solver
+
+    INPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+        EpsF    -   >=0
+                    The subroutine finishes  its work if on k+1-th iteration
+                    the condition ||F||<=EpsF is satisfied
+        MaxIts  -   maximum number of iterations. If MaxIts=0, the  number  of
+                    iterations is unlimited.
+
+    Passing EpsF=0 and MaxIts=0 simultaneously will lead to  automatic
+    stopping criterion selection (small EpsF).
+
+    NOTES:
+
+      -- ALGLIB --
+         Copyright 20.08.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void nleqsetcond(nleqstate state, double epsf, int maxits)
+    {
+
+        nleq.nleqsetcond(state.innerobj, epsf, maxits);
+        return;
+    }
+
+    /*************************************************************************
+    This function turns on/off reporting.
+
+    INPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+        NeedXRep-   whether iteration reports are needed or not
+
+    If NeedXRep is True, algorithm will call rep() callback function if  it is
+    provided to NLEQSolve().
+
+      -- ALGLIB --
+         Copyright 20.08.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void nleqsetxrep(nleqstate state, bool needxrep)
+    {
+
+        nleq.nleqsetxrep(state.innerobj, needxrep);
+        return;
+    }
+
+    /*************************************************************************
+    This function sets maximum step length
+
+    INPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+        StpMax  -   maximum step length, >=0. Set StpMax to 0.0,  if you don't
+                    want to limit step length.
+
+    Use this subroutine when target function  contains  exp()  or  other  fast
+    growing functions, and algorithm makes  too  large  steps  which  lead  to
+    overflow. This function allows us to reject steps that are too large  (and
+    therefore expose us to the possible overflow) without actually calculating
+    function value at the x+stp*d.
+
+      -- ALGLIB --
+         Copyright 20.08.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void nleqsetstpmax(nleqstate state, double stpmax)
+    {
+
+        nleq.nleqsetstpmax(state.innerobj, stpmax);
+        return;
+    }
+
+    /*************************************************************************
+    This function provides reverse communication interface
+    Reverse communication interface is not documented or recommended to use.
+    See below for functions which provide better documented API
+    *************************************************************************/
+    public static bool nleqiteration(nleqstate state)
+    {
+
+        bool result = nleq.nleqiteration(state.innerobj);
+        return result;
+    }
+    /*************************************************************************
+    This family of functions is used to launcn iterations of nonlinear solver
+
+    These functions accept following parameters:
+        func    -   callback which calculates function (or merit function)
+                    value func at given point x
+        jac     -   callback which calculates function vector fi[]
+                    and Jacobian jac at given point x
+        rep     -   optional callback which is called after each iteration
+                    can be null
+        obj     -   optional object which is passed to func/grad/hess/jac/rep
+                    can be null
+
+
+      -- ALGLIB --
+         Copyright 20.03.2009 by Bochkanov Sergey
+
+    *************************************************************************/
+    public static void nleqsolve(nleqstate state, ndimensional_func func, ndimensional_jac  jac, ndimensional_rep rep, object obj)
+    {
+        if( func==null )
+            throw new alglibexception("ALGLIB: error in 'nleqsolve()' (func is null)");
+        if( jac==null )
+            throw new alglibexception("ALGLIB: error in 'nleqsolve()' (jac is null)");
+        while( alglib.nleqiteration(state) )
+        {
+            if( state.needf )
+            {
+                func(state.x, ref state.innerobj.f, obj);
+                continue;
+            }
+            if( state.needfij )
+            {
+                jac(state.x, state.innerobj.fi, state.innerobj.j, obj);
+                continue;
+            }
+            if( state.innerobj.xupdated )
+            {
+                if( rep!=null )
+                    rep(state.innerobj.x, state.innerobj.f, obj);
+                continue;
+            }
+            throw new alglibexception("ALGLIB: error in 'nleqsolve' (some derivatives were not provided?)");
+        }
+    }
+
+
+
+    /*************************************************************************
+    NLEQ solver results
+
+    INPUT PARAMETERS:
+        State   -   algorithm state.
+
+    OUTPUT PARAMETERS:
+        X       -   array[0..N-1], solution
+        Rep     -   optimization report:
+                    * Rep.TerminationType completetion code:
+                        * -4    ERROR:  algorithm   has   converged   to   the
+                                stationary point Xf which is local minimum  of
+                                f=F[0]^2+...+F[m-1]^2, but is not solution  of
+                                nonlinear system.
+                        *  1    sqrt(f)<=EpsF.
+                        *  5    MaxIts steps was taken
+                        *  7    stopping conditions are too stringent,
+                                further improvement is impossible
+                    * Rep.IterationsCount contains iterations count
+                    * NFEV countains number of function calculations
+                    * ActiveConstraints contains number of active constraints
+
+      -- ALGLIB --
+         Copyright 20.08.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void nleqresults(nleqstate state, out double[] x, out nleqreport rep)
+    {
+        x = new double[0];
+        rep = new nleqreport();
+        nleq.nleqresults(state.innerobj, ref x, rep.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    NLEQ solver results
+
+    Buffered implementation of NLEQResults(), which uses pre-allocated  buffer
+    to store X[]. If buffer size is  too  small,  it  resizes  buffer.  It  is
+    intended to be used in the inner cycles of performance critical algorithms
+    where array reallocation penalty is too large to be ignored.
+
+      -- ALGLIB --
+         Copyright 20.08.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void nleqresultsbuf(nleqstate state, ref double[] x, nleqreport rep)
+    {
+
+        nleq.nleqresultsbuf(state.innerobj, ref x, rep.innerobj);
+        return;
+    }
+
+    /*************************************************************************
+    This  subroutine  restarts  CG  algorithm from new point. All optimization
+    parameters are left unchanged.
+
+    This  function  allows  to  solve multiple  optimization  problems  (which
+    must have same number of dimensions) without object reallocation penalty.
+
+    INPUT PARAMETERS:
+        State   -   structure used for reverse communication previously
+                    allocated with MinCGCreate call.
+        X       -   new starting point.
+        BndL    -   new lower bounds
+        BndU    -   new upper bounds
+
+      -- ALGLIB --
+         Copyright 30.07.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void nleqrestartfrom(nleqstate state, double[] x)
+    {
+
+        nleq.nleqrestartfrom(state.innerobj, x);
+        return;
+    }
+
+}
+public partial class alglib
+{
+    public class densesolver
+    {
+        public class densesolverreport
+        {
+            public double r1;
+            public double rinf;
+        };
+
+
+        public class densesolverlsreport
+        {
+            public double r2;
+            public double[,] cx;
+            public int n;
+            public int k;
+            public densesolverlsreport()
+            {
+                cx = new double[0,0];
+            }
+        };
+
+
+
+
+        /*************************************************************************
+        Dense solver.
+
+        This  subroutine  solves  a  system  A*x=b,  where A is NxN non-denegerate
+        real matrix, x and b are vectors.
+
+        Algorithm features:
+        * automatic detection of degenerate cases
+        * condition number estimation
+        * iterative refinement
+        * O(N^3) complexity
+
+        INPUT PARAMETERS
+            A       -   array[0..N-1,0..N-1], system matrix
+            N       -   size of A
+            B       -   array[0..N-1], right part
+
+        OUTPUT PARAMETERS
+            Info    -   return code:
+                        * -3    A is singular, or VERY close to singular.
+                                X is filled by zeros in such cases.
+                        * -1    N<=0 was passed
+                        *  1    task is solved (but matrix A may be ill-conditioned,
+                                check R1/RInf parameters for condition numbers).
+            Rep     -   solver report, see below for more info
+            X       -   array[0..N-1], it contains:
+                        * solution of A*x=b if A is non-singular (well-conditioned
+                          or ill-conditioned, but not very close to singular)
+                        * zeros,  if  A  is  singular  or  VERY  close to singular
+                          (in this case Info=-3).
+
+        SOLVER REPORT
+
+        Subroutine sets following fields of the Rep structure:
+        * R1        reciprocal of condition number: 1/cond(A), 1-norm.
+        * RInf      reciprocal of condition number: 1/cond(A), inf-norm.
+
+          -- ALGLIB --
+             Copyright 27.01.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rmatrixsolve(double[,] a,
+            int n,
+            double[] b,
+            ref int info,
+            densesolverreport rep,
+            ref double[] x)
+        {
+            double[,] bm = new double[0,0];
+            double[,] xm = new double[0,0];
+            int i_ = 0;
+
+            info = 0;
+            x = new double[0];
+
+            if( n<=0 )
+            {
+                info = -1;
+                return;
+            }
+            bm = new double[n, 1];
+            for(i_=0; i_<=n-1;i_++)
+            {
+                bm[i_,0] = b[i_];
+            }
+            rmatrixsolvem(a, n, bm, 1, true, ref info, rep, ref xm);
+            x = new double[n];
+            for(i_=0; i_<=n-1;i_++)
+            {
+                x[i_] = xm[i_,0];
+            }
+        }
+
+
+        /*************************************************************************
+        Dense solver.
+
+        Similar to RMatrixSolve() but solves task with multiple right parts (where
+        b and x are NxM matrices).
+
+        Algorithm features:
+        * automatic detection of degenerate cases
+        * condition number estimation
+        * optional iterative refinement
+        * O(N^3+M*N^2) complexity
+
+        INPUT PARAMETERS
+            A       -   array[0..N-1,0..N-1], system matrix
+            N       -   size of A
+            B       -   array[0..N-1,0..M-1], right part
+            M       -   right part size
+            RFS     -   iterative refinement switch:
+                        * True - refinement is used.
+                          Less performance, more precision.
+                        * False - refinement is not used.
+                          More performance, less precision.
+
+        OUTPUT PARAMETERS
+            Info    -   same as in RMatrixSolve
+            Rep     -   same as in RMatrixSolve
+            X       -   same as in RMatrixSolve
+
+          -- ALGLIB --
+             Copyright 27.01.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rmatrixsolvem(double[,] a,
+            int n,
+            double[,] b,
+            int m,
+            bool rfs,
+            ref int info,
+            densesolverreport rep,
+            ref double[,] x)
+        {
+            double[,] da = new double[0,0];
+            double[,] emptya = new double[0,0];
+            int[] p = new int[0];
+            double scalea = 0;
+            int i = 0;
+            int j = 0;
+            int i_ = 0;
+
+            info = 0;
+            x = new double[0,0];
+
+            
+            //
+            // prepare: check inputs, allocate space...
+            //
+            if( n<=0 || m<=0 )
+            {
+                info = -1;
+                return;
+            }
+            da = new double[n, n];
+            
+            //
+            // 1. scale matrix, max(|A[i,j]|)
+            // 2. factorize scaled matrix
+            // 3. solve
+            //
+            scalea = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                for(j=0; j<=n-1; j++)
+                {
+                    scalea = Math.Max(scalea, Math.Abs(a[i,j]));
+                }
+            }
+            if( (double)(scalea)==(double)(0) )
+            {
+                scalea = 1;
+            }
+            scalea = 1/scalea;
+            for(i=0; i<=n-1; i++)
+            {
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    da[i,i_] = a[i,i_];
+                }
+            }
+            trfac.rmatrixlu(ref da, n, n, ref p);
+            if( rfs )
+            {
+                rmatrixlusolveinternal(da, p, scalea, n, a, true, b, m, ref info, rep, ref x);
+            }
+            else
+            {
+                rmatrixlusolveinternal(da, p, scalea, n, emptya, false, b, m, ref info, rep, ref x);
+            }
+        }
+
+
+        /*************************************************************************
+        Dense solver.
+
+        This  subroutine  solves  a  system  A*X=B,  where A is NxN non-denegerate
+        real matrix given by its LU decomposition, X and B are NxM real matrices.
+
+        Algorithm features:
+        * automatic detection of degenerate cases
+        * O(N^2) complexity
+        * condition number estimation
+
+        No iterative refinement  is provided because exact form of original matrix
+        is not known to subroutine. Use RMatrixSolve or RMatrixMixedSolve  if  you
+        need iterative refinement.
+
+        INPUT PARAMETERS
+            LUA     -   array[0..N-1,0..N-1], LU decomposition, RMatrixLU result
+            P       -   array[0..N-1], pivots array, RMatrixLU result
+            N       -   size of A
+            B       -   array[0..N-1], right part
+
+        OUTPUT PARAMETERS
+            Info    -   same as in RMatrixSolve
+            Rep     -   same as in RMatrixSolve
+            X       -   same as in RMatrixSolve
+            
+          -- ALGLIB --
+             Copyright 27.01.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rmatrixlusolve(double[,] lua,
+            int[] p,
+            int n,
+            double[] b,
+            ref int info,
+            densesolverreport rep,
+            ref double[] x)
+        {
+            double[,] bm = new double[0,0];
+            double[,] xm = new double[0,0];
+            int i_ = 0;
+
+            info = 0;
+            x = new double[0];
+
+            if( n<=0 )
+            {
+                info = -1;
+                return;
+            }
+            bm = new double[n, 1];
+            for(i_=0; i_<=n-1;i_++)
+            {
+                bm[i_,0] = b[i_];
+            }
+            rmatrixlusolvem(lua, p, n, bm, 1, ref info, rep, ref xm);
+            x = new double[n];
+            for(i_=0; i_<=n-1;i_++)
+            {
+                x[i_] = xm[i_,0];
+            }
+        }
+
+
+        /*************************************************************************
+        Dense solver.
+
+        Similar to RMatrixLUSolve() but solves task with multiple right parts
+        (where b and x are NxM matrices).
+
+        Algorithm features:
+        * automatic detection of degenerate cases
+        * O(M*N^2) complexity
+        * condition number estimation
+
+        No iterative refinement  is provided because exact form of original matrix
+        is not known to subroutine. Use RMatrixSolve or RMatrixMixedSolve  if  you
+        need iterative refinement.
+
+        INPUT PARAMETERS
+            LUA     -   array[0..N-1,0..N-1], LU decomposition, RMatrixLU result
+            P       -   array[0..N-1], pivots array, RMatrixLU result
+            N       -   size of A
+            B       -   array[0..N-1,0..M-1], right part
+            M       -   right part size
+
+        OUTPUT PARAMETERS
+            Info    -   same as in RMatrixSolve
+            Rep     -   same as in RMatrixSolve
+            X       -   same as in RMatrixSolve
+
+          -- ALGLIB --
+             Copyright 27.01.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rmatrixlusolvem(double[,] lua,
+            int[] p,
+            int n,
+            double[,] b,
+            int m,
+            ref int info,
+            densesolverreport rep,
+            ref double[,] x)
+        {
+            double[,] emptya = new double[0,0];
+            int i = 0;
+            int j = 0;
+            double scalea = 0;
+
+            info = 0;
+            x = new double[0,0];
+
+            
+            //
+            // prepare: check inputs, allocate space...
+            //
+            if( n<=0 || m<=0 )
+            {
+                info = -1;
+                return;
+            }
+            
+            //
+            // 1. scale matrix, max(|U[i,j]|)
+            //    we assume that LU is in its normal form, i.e. |L[i,j]|<=1
+            // 2. solve
+            //
+            scalea = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                for(j=i; j<=n-1; j++)
+                {
+                    scalea = Math.Max(scalea, Math.Abs(lua[i,j]));
+                }
+            }
+            if( (double)(scalea)==(double)(0) )
+            {
+                scalea = 1;
+            }
+            scalea = 1/scalea;
+            rmatrixlusolveinternal(lua, p, scalea, n, emptya, false, b, m, ref info, rep, ref x);
+        }
+
+
+        /*************************************************************************
+        Dense solver.
+
+        This  subroutine  solves  a  system  A*x=b,  where BOTH ORIGINAL A AND ITS
+        LU DECOMPOSITION ARE KNOWN. You can use it if for some  reasons  you  have
+        both A and its LU decomposition.
+
+        Algorithm features:
+        * automatic detection of degenerate cases
+        * condition number estimation
+        * iterative refinement
+        * O(N^2) complexity
+
+        INPUT PARAMETERS
+            A       -   array[0..N-1,0..N-1], system matrix
+            LUA     -   array[0..N-1,0..N-1], LU decomposition, RMatrixLU result
+            P       -   array[0..N-1], pivots array, RMatrixLU result
+            N       -   size of A
+            B       -   array[0..N-1], right part
+
+        OUTPUT PARAMETERS
+            Info    -   same as in RMatrixSolveM
+            Rep     -   same as in RMatrixSolveM
+            X       -   same as in RMatrixSolveM
+
+          -- ALGLIB --
+             Copyright 27.01.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rmatrixmixedsolve(double[,] a,
+            double[,] lua,
+            int[] p,
+            int n,
+            double[] b,
+            ref int info,
+            densesolverreport rep,
+            ref double[] x)
+        {
+            double[,] bm = new double[0,0];
+            double[,] xm = new double[0,0];
+            int i_ = 0;
+
+            info = 0;
+            x = new double[0];
+
+            if( n<=0 )
+            {
+                info = -1;
+                return;
+            }
+            bm = new double[n, 1];
+            for(i_=0; i_<=n-1;i_++)
+            {
+                bm[i_,0] = b[i_];
+            }
+            rmatrixmixedsolvem(a, lua, p, n, bm, 1, ref info, rep, ref xm);
+            x = new double[n];
+            for(i_=0; i_<=n-1;i_++)
+            {
+                x[i_] = xm[i_,0];
+            }
+        }
+
+
+        /*************************************************************************
+        Dense solver.
+
+        Similar to RMatrixMixedSolve() but  solves task with multiple right  parts
+        (where b and x are NxM matrices).
+
+        Algorithm features:
+        * automatic detection of degenerate cases
+        * condition number estimation
+        * iterative refinement
+        * O(M*N^2) complexity
+
+        INPUT PARAMETERS
+            A       -   array[0..N-1,0..N-1], system matrix
+            LUA     -   array[0..N-1,0..N-1], LU decomposition, RMatrixLU result
+            P       -   array[0..N-1], pivots array, RMatrixLU result
+            N       -   size of A
+            B       -   array[0..N-1,0..M-1], right part
+            M       -   right part size
+
+        OUTPUT PARAMETERS
+            Info    -   same as in RMatrixSolveM
+            Rep     -   same as in RMatrixSolveM
+            X       -   same as in RMatrixSolveM
+
+          -- ALGLIB --
+             Copyright 27.01.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rmatrixmixedsolvem(double[,] a,
+            double[,] lua,
+            int[] p,
+            int n,
+            double[,] b,
+            int m,
+            ref int info,
+            densesolverreport rep,
+            ref double[,] x)
+        {
+            double scalea = 0;
+            int i = 0;
+            int j = 0;
+
+            info = 0;
+            x = new double[0,0];
+
+            
+            //
+            // prepare: check inputs, allocate space...
+            //
+            if( n<=0 || m<=0 )
+            {
+                info = -1;
+                return;
+            }
+            
+            //
+            // 1. scale matrix, max(|A[i,j]|)
+            // 2. factorize scaled matrix
+            // 3. solve
+            //
+            scalea = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                for(j=0; j<=n-1; j++)
+                {
+                    scalea = Math.Max(scalea, Math.Abs(a[i,j]));
+                }
+            }
+            if( (double)(scalea)==(double)(0) )
+            {
+                scalea = 1;
+            }
+            scalea = 1/scalea;
+            rmatrixlusolveinternal(lua, p, scalea, n, a, true, b, m, ref info, rep, ref x);
+        }
+
+
+        /*************************************************************************
+        Dense solver. Same as RMatrixSolveM(), but for complex matrices.
+
+        Algorithm features:
+        * automatic detection of degenerate cases
+        * condition number estimation
+        * iterative refinement
+        * O(N^3+M*N^2) complexity
+
+        INPUT PARAMETERS
+            A       -   array[0..N-1,0..N-1], system matrix
+            N       -   size of A
+            B       -   array[0..N-1,0..M-1], right part
+            M       -   right part size
+            RFS     -   iterative refinement switch:
+                        * True - refinement is used.
+                          Less performance, more precision.
+                        * False - refinement is not used.
+                          More performance, less precision.
+
+        OUTPUT PARAMETERS
+            Info    -   same as in RMatrixSolve
+            Rep     -   same as in RMatrixSolve
+            X       -   same as in RMatrixSolve
+
+          -- ALGLIB --
+             Copyright 27.01.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void cmatrixsolvem(complex[,] a,
+            int n,
+            complex[,] b,
+            int m,
+            bool rfs,
+            ref int info,
+            densesolverreport rep,
+            ref complex[,] x)
+        {
+            complex[,] da = new complex[0,0];
+            complex[,] emptya = new complex[0,0];
+            int[] p = new int[0];
+            double scalea = 0;
+            int i = 0;
+            int j = 0;
+            int i_ = 0;
+
+            info = 0;
+            x = new complex[0,0];
+
+            
+            //
+            // prepare: check inputs, allocate space...
+            //
+            if( n<=0 || m<=0 )
+            {
+                info = -1;
+                return;
+            }
+            da = new complex[n, n];
+            
+            //
+            // 1. scale matrix, max(|A[i,j]|)
+            // 2. factorize scaled matrix
+            // 3. solve
+            //
+            scalea = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                for(j=0; j<=n-1; j++)
+                {
+                    scalea = Math.Max(scalea, math.abscomplex(a[i,j]));
+                }
+            }
+            if( (double)(scalea)==(double)(0) )
+            {
+                scalea = 1;
+            }
+            scalea = 1/scalea;
+            for(i=0; i<=n-1; i++)
+            {
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    da[i,i_] = a[i,i_];
+                }
+            }
+            trfac.cmatrixlu(ref da, n, n, ref p);
+            if( rfs )
+            {
+                cmatrixlusolveinternal(da, p, scalea, n, a, true, b, m, ref info, rep, ref x);
+            }
+            else
+            {
+                cmatrixlusolveinternal(da, p, scalea, n, emptya, false, b, m, ref info, rep, ref x);
+            }
+        }
+
+
+        /*************************************************************************
+        Dense solver. Same as RMatrixSolve(), but for complex matrices.
+
+        Algorithm features:
+        * automatic detection of degenerate cases
+        * condition number estimation
+        * iterative refinement
+        * O(N^3) complexity
+
+        INPUT PARAMETERS
+            A       -   array[0..N-1,0..N-1], system matrix
+            N       -   size of A
+            B       -   array[0..N-1], right part
+
+        OUTPUT PARAMETERS
+            Info    -   same as in RMatrixSolve
+            Rep     -   same as in RMatrixSolve
+            X       -   same as in RMatrixSolve
+
+          -- ALGLIB --
+             Copyright 27.01.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void cmatrixsolve(complex[,] a,
+            int n,
+            complex[] b,
+            ref int info,
+            densesolverreport rep,
+            ref complex[] x)
+        {
+            complex[,] bm = new complex[0,0];
+            complex[,] xm = new complex[0,0];
+            int i_ = 0;
+
+            info = 0;
+            x = new complex[0];
+
+            if( n<=0 )
+            {
+                info = -1;
+                return;
+            }
+            bm = new complex[n, 1];
+            for(i_=0; i_<=n-1;i_++)
+            {
+                bm[i_,0] = b[i_];
+            }
+            cmatrixsolvem(a, n, bm, 1, true, ref info, rep, ref xm);
+            x = new complex[n];
+            for(i_=0; i_<=n-1;i_++)
+            {
+                x[i_] = xm[i_,0];
+            }
+        }
+
+
+        /*************************************************************************
+        Dense solver. Same as RMatrixLUSolveM(), but for complex matrices.
+
+        Algorithm features:
+        * automatic detection of degenerate cases
+        * O(M*N^2) complexity
+        * condition number estimation
+
+        No iterative refinement  is provided because exact form of original matrix
+        is not known to subroutine. Use CMatrixSolve or CMatrixMixedSolve  if  you
+        need iterative refinement.
+
+        INPUT PARAMETERS
+            LUA     -   array[0..N-1,0..N-1], LU decomposition, RMatrixLU result
+            P       -   array[0..N-1], pivots array, RMatrixLU result
+            N       -   size of A
+            B       -   array[0..N-1,0..M-1], right part
+            M       -   right part size
+
+        OUTPUT PARAMETERS
+            Info    -   same as in RMatrixSolve
+            Rep     -   same as in RMatrixSolve
+            X       -   same as in RMatrixSolve
+
+          -- ALGLIB --
+             Copyright 27.01.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void cmatrixlusolvem(complex[,] lua,
+            int[] p,
+            int n,
+            complex[,] b,
+            int m,
+            ref int info,
+            densesolverreport rep,
+            ref complex[,] x)
+        {
+            complex[,] emptya = new complex[0,0];
+            int i = 0;
+            int j = 0;
+            double scalea = 0;
+
+            info = 0;
+            x = new complex[0,0];
+
+            
+            //
+            // prepare: check inputs, allocate space...
+            //
+            if( n<=0 || m<=0 )
+            {
+                info = -1;
+                return;
+            }
+            
+            //
+            // 1. scale matrix, max(|U[i,j]|)
+            //    we assume that LU is in its normal form, i.e. |L[i,j]|<=1
+            // 2. solve
+            //
+            scalea = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                for(j=i; j<=n-1; j++)
+                {
+                    scalea = Math.Max(scalea, math.abscomplex(lua[i,j]));
+                }
+            }
+            if( (double)(scalea)==(double)(0) )
+            {
+                scalea = 1;
+            }
+            scalea = 1/scalea;
+            cmatrixlusolveinternal(lua, p, scalea, n, emptya, false, b, m, ref info, rep, ref x);
+        }
+
+
+        /*************************************************************************
+        Dense solver. Same as RMatrixLUSolve(), but for complex matrices.
+
+        Algorithm features:
+        * automatic detection of degenerate cases
+        * O(N^2) complexity
+        * condition number estimation
+
+        No iterative refinement is provided because exact form of original matrix
+        is not known to subroutine. Use CMatrixSolve or CMatrixMixedSolve  if  you
+        need iterative refinement.
+
+        INPUT PARAMETERS
+            LUA     -   array[0..N-1,0..N-1], LU decomposition, CMatrixLU result
+            P       -   array[0..N-1], pivots array, CMatrixLU result
+            N       -   size of A
+            B       -   array[0..N-1], right part
+
+        OUTPUT PARAMETERS
+            Info    -   same as in RMatrixSolve
+            Rep     -   same as in RMatrixSolve
+            X       -   same as in RMatrixSolve
+
+          -- ALGLIB --
+             Copyright 27.01.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void cmatrixlusolve(complex[,] lua,
+            int[] p,
+            int n,
+            complex[] b,
+            ref int info,
+            densesolverreport rep,
+            ref complex[] x)
+        {
+            complex[,] bm = new complex[0,0];
+            complex[,] xm = new complex[0,0];
+            int i_ = 0;
+
+            info = 0;
+            x = new complex[0];
+
+            if( n<=0 )
+            {
+                info = -1;
+                return;
+            }
+            bm = new complex[n, 1];
+            for(i_=0; i_<=n-1;i_++)
+            {
+                bm[i_,0] = b[i_];
+            }
+            cmatrixlusolvem(lua, p, n, bm, 1, ref info, rep, ref xm);
+            x = new complex[n];
+            for(i_=0; i_<=n-1;i_++)
+            {
+                x[i_] = xm[i_,0];
+            }
+        }
+
+
+        /*************************************************************************
+        Dense solver. Same as RMatrixMixedSolveM(), but for complex matrices.
+
+        Algorithm features:
+        * automatic detection of degenerate cases
+        * condition number estimation
+        * iterative refinement
+        * O(M*N^2) complexity
+
+        INPUT PARAMETERS
+            A       -   array[0..N-1,0..N-1], system matrix
+            LUA     -   array[0..N-1,0..N-1], LU decomposition, CMatrixLU result
+            P       -   array[0..N-1], pivots array, CMatrixLU result
+            N       -   size of A
+            B       -   array[0..N-1,0..M-1], right part
+            M       -   right part size
+
+        OUTPUT PARAMETERS
+            Info    -   same as in RMatrixSolveM
+            Rep     -   same as in RMatrixSolveM
+            X       -   same as in RMatrixSolveM
+
+          -- ALGLIB --
+             Copyright 27.01.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void cmatrixmixedsolvem(complex[,] a,
+            complex[,] lua,
+            int[] p,
+            int n,
+            complex[,] b,
+            int m,
+            ref int info,
+            densesolverreport rep,
+            ref complex[,] x)
+        {
+            double scalea = 0;
+            int i = 0;
+            int j = 0;
+
+            info = 0;
+            x = new complex[0,0];
+
+            
+            //
+            // prepare: check inputs, allocate space...
+            //
+            if( n<=0 || m<=0 )
+            {
+                info = -1;
+                return;
+            }
+            
+            //
+            // 1. scale matrix, max(|A[i,j]|)
+            // 2. factorize scaled matrix
+            // 3. solve
+            //
+            scalea = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                for(j=0; j<=n-1; j++)
+                {
+                    scalea = Math.Max(scalea, math.abscomplex(a[i,j]));
+                }
+            }
+            if( (double)(scalea)==(double)(0) )
+            {
+                scalea = 1;
+            }
+            scalea = 1/scalea;
+            cmatrixlusolveinternal(lua, p, scalea, n, a, true, b, m, ref info, rep, ref x);
+        }
+
+
+        /*************************************************************************
+        Dense solver. Same as RMatrixMixedSolve(), but for complex matrices.
+
+        Algorithm features:
+        * automatic detection of degenerate cases
+        * condition number estimation
+        * iterative refinement
+        * O(N^2) complexity
+
+        INPUT PARAMETERS
+            A       -   array[0..N-1,0..N-1], system matrix
+            LUA     -   array[0..N-1,0..N-1], LU decomposition, CMatrixLU result
+            P       -   array[0..N-1], pivots array, CMatrixLU result
+            N       -   size of A
+            B       -   array[0..N-1], right part
+
+        OUTPUT PARAMETERS
+            Info    -   same as in RMatrixSolveM
+            Rep     -   same as in RMatrixSolveM
+            X       -   same as in RMatrixSolveM
+
+          -- ALGLIB --
+             Copyright 27.01.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void cmatrixmixedsolve(complex[,] a,
+            complex[,] lua,
+            int[] p,
+            int n,
+            complex[] b,
+            ref int info,
+            densesolverreport rep,
+            ref complex[] x)
+        {
+            complex[,] bm = new complex[0,0];
+            complex[,] xm = new complex[0,0];
+            int i_ = 0;
+
+            info = 0;
+            x = new complex[0];
+
+            if( n<=0 )
+            {
+                info = -1;
+                return;
+            }
+            bm = new complex[n, 1];
+            for(i_=0; i_<=n-1;i_++)
+            {
+                bm[i_,0] = b[i_];
+            }
+            cmatrixmixedsolvem(a, lua, p, n, bm, 1, ref info, rep, ref xm);
+            x = new complex[n];
+            for(i_=0; i_<=n-1;i_++)
+            {
+                x[i_] = xm[i_,0];
+            }
+        }
+
+
+        /*************************************************************************
+        Dense solver. Same as RMatrixSolveM(), but for symmetric positive definite
+        matrices.
+
+        Algorithm features:
+        * automatic detection of degenerate cases
+        * condition number estimation
+        * O(N^3+M*N^2) complexity
+        * matrix is represented by its upper or lower triangle
+
+        No iterative refinement is provided because such partial representation of
+        matrix does not allow efficient calculation of extra-precise  matrix-vector
+        products for large matrices. Use RMatrixSolve or RMatrixMixedSolve  if  you
+        need iterative refinement.
+
+        INPUT PARAMETERS
+            A       -   array[0..N-1,0..N-1], system matrix
+            N       -   size of A
+            IsUpper -   what half of A is provided
+            B       -   array[0..N-1,0..M-1], right part
+            M       -   right part size
+
+        OUTPUT PARAMETERS
+            Info    -   same as in RMatrixSolve.
+                        Returns -3 for non-SPD matrices.
+            Rep     -   same as in RMatrixSolve
+            X       -   same as in RMatrixSolve
+
+          -- ALGLIB --
+             Copyright 27.01.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spdmatrixsolvem(double[,] a,
+            int n,
+            bool isupper,
+            double[,] b,
+            int m,
+            ref int info,
+            densesolverreport rep,
+            ref double[,] x)
+        {
+            double[,] da = new double[0,0];
+            double sqrtscalea = 0;
+            int i = 0;
+            int j = 0;
+            int j1 = 0;
+            int j2 = 0;
+            int i_ = 0;
+
+            info = 0;
+            x = new double[0,0];
+
+            
+            //
+            // prepare: check inputs, allocate space...
+            //
+            if( n<=0 || m<=0 )
+            {
+                info = -1;
+                return;
+            }
+            da = new double[n, n];
+            
+            //
+            // 1. scale matrix, max(|A[i,j]|)
+            // 2. factorize scaled matrix
+            // 3. solve
+            //
+            sqrtscalea = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                if( isupper )
+                {
+                    j1 = i;
+                    j2 = n-1;
+                }
+                else
+                {
+                    j1 = 0;
+                    j2 = i;
+                }
+                for(j=j1; j<=j2; j++)
+                {
+                    sqrtscalea = Math.Max(sqrtscalea, Math.Abs(a[i,j]));
+                }
+            }
+            if( (double)(sqrtscalea)==(double)(0) )
+            {
+                sqrtscalea = 1;
+            }
+            sqrtscalea = 1/sqrtscalea;
+            sqrtscalea = Math.Sqrt(sqrtscalea);
+            for(i=0; i<=n-1; i++)
+            {
+                if( isupper )
+                {
+                    j1 = i;
+                    j2 = n-1;
+                }
+                else
+                {
+                    j1 = 0;
+                    j2 = i;
+                }
+                for(i_=j1; i_<=j2;i_++)
+                {
+                    da[i,i_] = a[i,i_];
+                }
+            }
+            if( !trfac.spdmatrixcholesky(ref da, n, isupper) )
+            {
+                x = new double[n, m];
+                for(i=0; i<=n-1; i++)
+                {
+                    for(j=0; j<=m-1; j++)
+                    {
+                        x[i,j] = 0;
+                    }
+                }
+                rep.r1 = 0;
+                rep.rinf = 0;
+                info = -3;
+                return;
+            }
+            info = 1;
+            spdmatrixcholeskysolveinternal(da, sqrtscalea, n, isupper, a, true, b, m, ref info, rep, ref x);
+        }
+
+
+        /*************************************************************************
+        Dense solver. Same as RMatrixSolve(), but for SPD matrices.
+
+        Algorithm features:
+        * automatic detection of degenerate cases
+        * condition number estimation
+        * O(N^3) complexity
+        * matrix is represented by its upper or lower triangle
+
+        No iterative refinement is provided because such partial representation of
+        matrix does not allow efficient calculation of extra-precise  matrix-vector
+        products for large matrices. Use RMatrixSolve or RMatrixMixedSolve  if  you
+        need iterative refinement.
+
+        INPUT PARAMETERS
+            A       -   array[0..N-1,0..N-1], system matrix
+            N       -   size of A
+            IsUpper -   what half of A is provided
+            B       -   array[0..N-1], right part
+
+        OUTPUT PARAMETERS
+            Info    -   same as in RMatrixSolve
+                        Returns -3 for non-SPD matrices.
+            Rep     -   same as in RMatrixSolve
+            X       -   same as in RMatrixSolve
+
+          -- ALGLIB --
+             Copyright 27.01.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spdmatrixsolve(double[,] a,
+            int n,
+            bool isupper,
+            double[] b,
+            ref int info,
+            densesolverreport rep,
+            ref double[] x)
+        {
+            double[,] bm = new double[0,0];
+            double[,] xm = new double[0,0];
+            int i_ = 0;
+
+            info = 0;
+            x = new double[0];
+
+            if( n<=0 )
+            {
+                info = -1;
+                return;
+            }
+            bm = new double[n, 1];
+            for(i_=0; i_<=n-1;i_++)
+            {
+                bm[i_,0] = b[i_];
+            }
+            spdmatrixsolvem(a, n, isupper, bm, 1, ref info, rep, ref xm);
+            x = new double[n];
+            for(i_=0; i_<=n-1;i_++)
+            {
+                x[i_] = xm[i_,0];
+            }
+        }
+
+
+        /*************************************************************************
+        Dense solver. Same as RMatrixLUSolveM(), but for SPD matrices  represented
+        by their Cholesky decomposition.
+
+        Algorithm features:
+        * automatic detection of degenerate cases
+        * O(M*N^2) complexity
+        * condition number estimation
+        * matrix is represented by its upper or lower triangle
+
+        No iterative refinement is provided because such partial representation of
+        matrix does not allow efficient calculation of extra-precise  matrix-vector
+        products for large matrices. Use RMatrixSolve or RMatrixMixedSolve  if  you
+        need iterative refinement.
+
+        INPUT PARAMETERS
+            CHA     -   array[0..N-1,0..N-1], Cholesky decomposition,
+                        SPDMatrixCholesky result
+            N       -   size of CHA
+            IsUpper -   what half of CHA is provided
+            B       -   array[0..N-1,0..M-1], right part
+            M       -   right part size
+
+        OUTPUT PARAMETERS
+            Info    -   same as in RMatrixSolve
+            Rep     -   same as in RMatrixSolve
+            X       -   same as in RMatrixSolve
+
+          -- ALGLIB --
+             Copyright 27.01.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spdmatrixcholeskysolvem(double[,] cha,
+            int n,
+            bool isupper,
+            double[,] b,
+            int m,
+            ref int info,
+            densesolverreport rep,
+            ref double[,] x)
+        {
+            double[,] emptya = new double[0,0];
+            double sqrtscalea = 0;
+            int i = 0;
+            int j = 0;
+            int j1 = 0;
+            int j2 = 0;
+
+            info = 0;
+            x = new double[0,0];
+
+            
+            //
+            // prepare: check inputs, allocate space...
+            //
+            if( n<=0 || m<=0 )
+            {
+                info = -1;
+                return;
+            }
+            
+            //
+            // 1. scale matrix, max(|U[i,j]|)
+            // 2. factorize scaled matrix
+            // 3. solve
+            //
+            sqrtscalea = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                if( isupper )
+                {
+                    j1 = i;
+                    j2 = n-1;
+                }
+                else
+                {
+                    j1 = 0;
+                    j2 = i;
+                }
+                for(j=j1; j<=j2; j++)
+                {
+                    sqrtscalea = Math.Max(sqrtscalea, Math.Abs(cha[i,j]));
+                }
+            }
+            if( (double)(sqrtscalea)==(double)(0) )
+            {
+                sqrtscalea = 1;
+            }
+            sqrtscalea = 1/sqrtscalea;
+            spdmatrixcholeskysolveinternal(cha, sqrtscalea, n, isupper, emptya, false, b, m, ref info, rep, ref x);
+        }
+
+
+        /*************************************************************************
+        Dense solver. Same as RMatrixLUSolve(), but for  SPD matrices  represented
+        by their Cholesky decomposition.
+
+        Algorithm features:
+        * automatic detection of degenerate cases
+        * O(N^2) complexity
+        * condition number estimation
+        * matrix is represented by its upper or lower triangle
+
+        No iterative refinement is provided because such partial representation of
+        matrix does not allow efficient calculation of extra-precise  matrix-vector
+        products for large matrices. Use RMatrixSolve or RMatrixMixedSolve  if  you
+        need iterative refinement.
+
+        INPUT PARAMETERS
+            CHA     -   array[0..N-1,0..N-1], Cholesky decomposition,
+                        SPDMatrixCholesky result
+            N       -   size of A
+            IsUpper -   what half of CHA is provided
+            B       -   array[0..N-1], right part
+
+        OUTPUT PARAMETERS
+            Info    -   same as in RMatrixSolve
+            Rep     -   same as in RMatrixSolve
+            X       -   same as in RMatrixSolve
+
+          -- ALGLIB --
+             Copyright 27.01.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spdmatrixcholeskysolve(double[,] cha,
+            int n,
+            bool isupper,
+            double[] b,
+            ref int info,
+            densesolverreport rep,
+            ref double[] x)
+        {
+            double[,] bm = new double[0,0];
+            double[,] xm = new double[0,0];
+            int i_ = 0;
+
+            info = 0;
+            x = new double[0];
+
+            if( n<=0 )
+            {
+                info = -1;
+                return;
+            }
+            bm = new double[n, 1];
+            for(i_=0; i_<=n-1;i_++)
+            {
+                bm[i_,0] = b[i_];
+            }
+            spdmatrixcholeskysolvem(cha, n, isupper, bm, 1, ref info, rep, ref xm);
+            x = new double[n];
+            for(i_=0; i_<=n-1;i_++)
+            {
+                x[i_] = xm[i_,0];
+            }
+        }
+
+
+        /*************************************************************************
+        Dense solver. Same as RMatrixSolveM(), but for Hermitian positive definite
+        matrices.
+
+        Algorithm features:
+        * automatic detection of degenerate cases
+        * condition number estimation
+        * O(N^3+M*N^2) complexity
+        * matrix is represented by its upper or lower triangle
+
+        No iterative refinement is provided because such partial representation of
+        matrix does not allow efficient calculation of extra-precise  matrix-vector
+        products for large matrices. Use RMatrixSolve or RMatrixMixedSolve  if  you
+        need iterative refinement.
+
+        INPUT PARAMETERS
+            A       -   array[0..N-1,0..N-1], system matrix
+            N       -   size of A
+            IsUpper -   what half of A is provided
+            B       -   array[0..N-1,0..M-1], right part
+            M       -   right part size
+
+        OUTPUT PARAMETERS
+            Info    -   same as in RMatrixSolve.
+                        Returns -3 for non-HPD matrices.
+            Rep     -   same as in RMatrixSolve
+            X       -   same as in RMatrixSolve
+
+          -- ALGLIB --
+             Copyright 27.01.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void hpdmatrixsolvem(complex[,] a,
+            int n,
+            bool isupper,
+            complex[,] b,
+            int m,
+            ref int info,
+            densesolverreport rep,
+            ref complex[,] x)
+        {
+            complex[,] da = new complex[0,0];
+            double sqrtscalea = 0;
+            int i = 0;
+            int j = 0;
+            int j1 = 0;
+            int j2 = 0;
+            int i_ = 0;
+
+            info = 0;
+            x = new complex[0,0];
+
+            
+            //
+            // prepare: check inputs, allocate space...
+            //
+            if( n<=0 || m<=0 )
+            {
+                info = -1;
+                return;
+            }
+            da = new complex[n, n];
+            
+            //
+            // 1. scale matrix, max(|A[i,j]|)
+            // 2. factorize scaled matrix
+            // 3. solve
+            //
+            sqrtscalea = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                if( isupper )
+                {
+                    j1 = i;
+                    j2 = n-1;
+                }
+                else
+                {
+                    j1 = 0;
+                    j2 = i;
+                }
+                for(j=j1; j<=j2; j++)
+                {
+                    sqrtscalea = Math.Max(sqrtscalea, math.abscomplex(a[i,j]));
+                }
+            }
+            if( (double)(sqrtscalea)==(double)(0) )
+            {
+                sqrtscalea = 1;
+            }
+            sqrtscalea = 1/sqrtscalea;
+            sqrtscalea = Math.Sqrt(sqrtscalea);
+            for(i=0; i<=n-1; i++)
+            {
+                if( isupper )
+                {
+                    j1 = i;
+                    j2 = n-1;
+                }
+                else
+                {
+                    j1 = 0;
+                    j2 = i;
+                }
+                for(i_=j1; i_<=j2;i_++)
+                {
+                    da[i,i_] = a[i,i_];
+                }
+            }
+            if( !trfac.hpdmatrixcholesky(ref da, n, isupper) )
+            {
+                x = new complex[n, m];
+                for(i=0; i<=n-1; i++)
+                {
+                    for(j=0; j<=m-1; j++)
+                    {
+                        x[i,j] = 0;
+                    }
+                }
+                rep.r1 = 0;
+                rep.rinf = 0;
+                info = -3;
+                return;
+            }
+            info = 1;
+            hpdmatrixcholeskysolveinternal(da, sqrtscalea, n, isupper, a, true, b, m, ref info, rep, ref x);
+        }
+
+
+        /*************************************************************************
+        Dense solver. Same as RMatrixSolve(),  but for Hermitian positive definite
+        matrices.
+
+        Algorithm features:
+        * automatic detection of degenerate cases
+        * condition number estimation
+        * O(N^3) complexity
+        * matrix is represented by its upper or lower triangle
+
+        No iterative refinement is provided because such partial representation of
+        matrix does not allow efficient calculation of extra-precise  matrix-vector
+        products for large matrices. Use RMatrixSolve or RMatrixMixedSolve  if  you
+        need iterative refinement.
+
+        INPUT PARAMETERS
+            A       -   array[0..N-1,0..N-1], system matrix
+            N       -   size of A
+            IsUpper -   what half of A is provided
+            B       -   array[0..N-1], right part
+
+        OUTPUT PARAMETERS
+            Info    -   same as in RMatrixSolve
+                        Returns -3 for non-HPD matrices.
+            Rep     -   same as in RMatrixSolve
+            X       -   same as in RMatrixSolve
+
+          -- ALGLIB --
+             Copyright 27.01.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void hpdmatrixsolve(complex[,] a,
+            int n,
+            bool isupper,
+            complex[] b,
+            ref int info,
+            densesolverreport rep,
+            ref complex[] x)
+        {
+            complex[,] bm = new complex[0,0];
+            complex[,] xm = new complex[0,0];
+            int i_ = 0;
+
+            info = 0;
+            x = new complex[0];
+
+            if( n<=0 )
+            {
+                info = -1;
+                return;
+            }
+            bm = new complex[n, 1];
+            for(i_=0; i_<=n-1;i_++)
+            {
+                bm[i_,0] = b[i_];
+            }
+            hpdmatrixsolvem(a, n, isupper, bm, 1, ref info, rep, ref xm);
+            x = new complex[n];
+            for(i_=0; i_<=n-1;i_++)
+            {
+                x[i_] = xm[i_,0];
+            }
+        }
+
+
+        /*************************************************************************
+        Dense solver. Same as RMatrixLUSolveM(), but for HPD matrices  represented
+        by their Cholesky decomposition.
+
+        Algorithm features:
+        * automatic detection of degenerate cases
+        * O(M*N^2) complexity
+        * condition number estimation
+        * matrix is represented by its upper or lower triangle
+
+        No iterative refinement is provided because such partial representation of
+        matrix does not allow efficient calculation of extra-precise  matrix-vector
+        products for large matrices. Use RMatrixSolve or RMatrixMixedSolve  if  you
+        need iterative refinement.
+
+        INPUT PARAMETERS
+            CHA     -   array[0..N-1,0..N-1], Cholesky decomposition,
+                        HPDMatrixCholesky result
+            N       -   size of CHA
+            IsUpper -   what half of CHA is provided
+            B       -   array[0..N-1,0..M-1], right part
+            M       -   right part size
+
+        OUTPUT PARAMETERS
+            Info    -   same as in RMatrixSolve
+            Rep     -   same as in RMatrixSolve
+            X       -   same as in RMatrixSolve
+
+          -- ALGLIB --
+             Copyright 27.01.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void hpdmatrixcholeskysolvem(complex[,] cha,
+            int n,
+            bool isupper,
+            complex[,] b,
+            int m,
+            ref int info,
+            densesolverreport rep,
+            ref complex[,] x)
+        {
+            complex[,] emptya = new complex[0,0];
+            double sqrtscalea = 0;
+            int i = 0;
+            int j = 0;
+            int j1 = 0;
+            int j2 = 0;
+
+            info = 0;
+            x = new complex[0,0];
+
+            
+            //
+            // prepare: check inputs, allocate space...
+            //
+            if( n<=0 || m<=0 )
+            {
+                info = -1;
+                return;
+            }
+            
+            //
+            // 1. scale matrix, max(|U[i,j]|)
+            // 2. factorize scaled matrix
+            // 3. solve
+            //
+            sqrtscalea = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                if( isupper )
+                {
+                    j1 = i;
+                    j2 = n-1;
+                }
+                else
+                {
+                    j1 = 0;
+                    j2 = i;
+                }
+                for(j=j1; j<=j2; j++)
+                {
+                    sqrtscalea = Math.Max(sqrtscalea, math.abscomplex(cha[i,j]));
+                }
+            }
+            if( (double)(sqrtscalea)==(double)(0) )
+            {
+                sqrtscalea = 1;
+            }
+            sqrtscalea = 1/sqrtscalea;
+            hpdmatrixcholeskysolveinternal(cha, sqrtscalea, n, isupper, emptya, false, b, m, ref info, rep, ref x);
+        }
+
+
+        /*************************************************************************
+        Dense solver. Same as RMatrixLUSolve(), but for  HPD matrices  represented
+        by their Cholesky decomposition.
+
+        Algorithm features:
+        * automatic detection of degenerate cases
+        * O(N^2) complexity
+        * condition number estimation
+        * matrix is represented by its upper or lower triangle
+
+        No iterative refinement is provided because such partial representation of
+        matrix does not allow efficient calculation of extra-precise  matrix-vector
+        products for large matrices. Use RMatrixSolve or RMatrixMixedSolve  if  you
+        need iterative refinement.
+
+        INPUT PARAMETERS
+            CHA     -   array[0..N-1,0..N-1], Cholesky decomposition,
+                        SPDMatrixCholesky result
+            N       -   size of A
+            IsUpper -   what half of CHA is provided
+            B       -   array[0..N-1], right part
+
+        OUTPUT PARAMETERS
+            Info    -   same as in RMatrixSolve
+            Rep     -   same as in RMatrixSolve
+            X       -   same as in RMatrixSolve
+
+          -- ALGLIB --
+             Copyright 27.01.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void hpdmatrixcholeskysolve(complex[,] cha,
+            int n,
+            bool isupper,
+            complex[] b,
+            ref int info,
+            densesolverreport rep,
+            ref complex[] x)
+        {
+            complex[,] bm = new complex[0,0];
+            complex[,] xm = new complex[0,0];
+            int i_ = 0;
+
+            info = 0;
+            x = new complex[0];
+
+            if( n<=0 )
+            {
+                info = -1;
+                return;
+            }
+            bm = new complex[n, 1];
+            for(i_=0; i_<=n-1;i_++)
+            {
+                bm[i_,0] = b[i_];
+            }
+            hpdmatrixcholeskysolvem(cha, n, isupper, bm, 1, ref info, rep, ref xm);
+            x = new complex[n];
+            for(i_=0; i_<=n-1;i_++)
+            {
+                x[i_] = xm[i_,0];
+            }
+        }
+
+
+        /*************************************************************************
+        Dense solver.
+
+        This subroutine finds solution of the linear system A*X=B with non-square,
+        possibly degenerate A.  System  is  solved in the least squares sense, and
+        general least squares solution  X = X0 + CX*y  which  minimizes |A*X-B| is
+        returned. If A is non-degenerate, solution in the  usual sense is returned
+
+        Algorithm features:
+        * automatic detection of degenerate cases
+        * iterative refinement
+        * O(N^3) complexity
+
+        INPUT PARAMETERS
+            A       -   array[0..NRows-1,0..NCols-1], system matrix
+            NRows   -   vertical size of A
+            NCols   -   horizontal size of A
+            B       -   array[0..NCols-1], right part
+            Threshold-  a number in [0,1]. Singular values  beyond  Threshold  are
+                        considered  zero.  Set  it to 0.0, if you don't understand
+                        what it means, so the solver will choose good value on its
+                        own.
+                        
+        OUTPUT PARAMETERS
+            Info    -   return code:
+                        * -4    SVD subroutine failed
+                        * -1    if NRows<=0 or NCols<=0 or Threshold<0 was passed
+                        *  1    if task is solved
+            Rep     -   solver report, see below for more info
+            X       -   array[0..N-1,0..M-1], it contains:
+                        * solution of A*X=B if A is non-singular (well-conditioned
+                          or ill-conditioned, but not very close to singular)
+                        * zeros,  if  A  is  singular  or  VERY  close to singular
+                          (in this case Info=-3).
+
+        SOLVER REPORT
+
+        Subroutine sets following fields of the Rep structure:
+        * R2        reciprocal of condition number: 1/cond(A), 2-norm.
+        * N         = NCols
+        * K         dim(Null(A))
+        * CX        array[0..N-1,0..K-1], kernel of A.
+                    Columns of CX store such vectors that A*CX[i]=0.
+
+          -- ALGLIB --
+             Copyright 24.08.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rmatrixsolvels(double[,] a,
+            int nrows,
+            int ncols,
+            double[] b,
+            double threshold,
+            ref int info,
+            densesolverlsreport rep,
+            ref double[] x)
+        {
+            double[] sv = new double[0];
+            double[,] u = new double[0,0];
+            double[,] vt = new double[0,0];
+            double[] rp = new double[0];
+            double[] utb = new double[0];
+            double[] sutb = new double[0];
+            double[] tmp = new double[0];
+            double[] ta = new double[0];
+            double[] tx = new double[0];
+            double[] buf = new double[0];
+            double[] w = new double[0];
+            int i = 0;
+            int j = 0;
+            int nsv = 0;
+            int kernelidx = 0;
+            double v = 0;
+            double verr = 0;
+            bool svdfailed = new bool();
+            bool zeroa = new bool();
+            int rfs = 0;
+            int nrfs = 0;
+            bool terminatenexttime = new bool();
+            bool smallerr = new bool();
+            int i_ = 0;
+
+            info = 0;
+            x = new double[0];
+
+            if( (nrows<=0 || ncols<=0) || (double)(threshold)<(double)(0) )
+            {
+                info = -1;
+                return;
+            }
+            if( (double)(threshold)==(double)(0) )
+            {
+                threshold = 1000*math.machineepsilon;
+            }
+            
+            //
+            // Factorize A first
+            //
+            svdfailed = !svd.rmatrixsvd(a, nrows, ncols, 1, 2, 2, ref sv, ref u, ref vt);
+            zeroa = (double)(sv[0])==(double)(0);
+            if( svdfailed || zeroa )
+            {
+                if( svdfailed )
+                {
+                    info = -4;
+                }
+                else
+                {
+                    info = 1;
+                }
+                x = new double[ncols];
+                for(i=0; i<=ncols-1; i++)
+                {
+                    x[i] = 0;
+                }
+                rep.n = ncols;
+                rep.k = ncols;
+                rep.cx = new double[ncols, ncols];
+                for(i=0; i<=ncols-1; i++)
+                {
+                    for(j=0; j<=ncols-1; j++)
+                    {
+                        if( i==j )
+                        {
+                            rep.cx[i,j] = 1;
+                        }
+                        else
+                        {
+                            rep.cx[i,j] = 0;
+                        }
+                    }
+                }
+                rep.r2 = 0;
+                return;
+            }
+            nsv = Math.Min(ncols, nrows);
+            if( nsv==ncols )
+            {
+                rep.r2 = sv[nsv-1]/sv[0];
+            }
+            else
+            {
+                rep.r2 = 0;
+            }
+            rep.n = ncols;
+            info = 1;
+            
+            //
+            // Iterative refinement of xc combined with solution:
+            // 1. xc = 0
+            // 2. calculate r = bc-A*xc using extra-precise dot product
+            // 3. solve A*y = r
+            // 4. update x:=x+r
+            // 5. goto 2
+            //
+            // This cycle is executed until one of two things happens:
+            // 1. maximum number of iterations reached
+            // 2. last iteration decreased error to the lower limit
+            //
+            utb = new double[nsv];
+            sutb = new double[nsv];
+            x = new double[ncols];
+            tmp = new double[ncols];
+            ta = new double[ncols+1];
+            tx = new double[ncols+1];
+            buf = new double[ncols+1];
+            for(i=0; i<=ncols-1; i++)
+            {
+                x[i] = 0;
+            }
+            kernelidx = nsv;
+            for(i=0; i<=nsv-1; i++)
+            {
+                if( (double)(sv[i])<=(double)(threshold*sv[0]) )
+                {
+                    kernelidx = i;
+                    break;
+                }
+            }
+            rep.k = ncols-kernelidx;
+            nrfs = densesolverrfsmaxv2(ncols, rep.r2);
+            terminatenexttime = false;
+            rp = new double[nrows];
+            for(rfs=0; rfs<=nrfs; rfs++)
+            {
+                if( terminatenexttime )
+                {
+                    break;
+                }
+                
+                //
+                // calculate right part
+                //
+                if( rfs==0 )
+                {
+                    for(i_=0; i_<=nrows-1;i_++)
+                    {
+                        rp[i_] = b[i_];
+                    }
+                }
+                else
+                {
+                    smallerr = true;
+                    for(i=0; i<=nrows-1; i++)
+                    {
+                        for(i_=0; i_<=ncols-1;i_++)
+                        {
+                            ta[i_] = a[i,i_];
+                        }
+                        ta[ncols] = -1;
+                        for(i_=0; i_<=ncols-1;i_++)
+                        {
+                            tx[i_] = x[i_];
+                        }
+                        tx[ncols] = b[i];
+                        xblas.xdot(ta, tx, ncols+1, ref buf, ref v, ref verr);
+                        rp[i] = -v;
+                        smallerr = smallerr && (double)(Math.Abs(v))<(double)(4*verr);
+                    }
+                    if( smallerr )
+                    {
+                        terminatenexttime = true;
+                    }
+                }
+                
+                //
+                // solve A*dx = rp
+                //
+                for(i=0; i<=ncols-1; i++)
+                {
+                    tmp[i] = 0;
+                }
+                for(i=0; i<=nsv-1; i++)
+                {
+                    utb[i] = 0;
+                }
+                for(i=0; i<=nrows-1; i++)
+                {
+                    v = rp[i];
+                    for(i_=0; i_<=nsv-1;i_++)
+                    {
+                        utb[i_] = utb[i_] + v*u[i,i_];
+                    }
+                }
+                for(i=0; i<=nsv-1; i++)
+                {
+                    if( i<kernelidx )
+                    {
+                        sutb[i] = utb[i]/sv[i];
+                    }
+                    else
+                    {
+                        sutb[i] = 0;
+                    }
+                }
+                for(i=0; i<=nsv-1; i++)
+                {
+                    v = sutb[i];
+                    for(i_=0; i_<=ncols-1;i_++)
+                    {
+                        tmp[i_] = tmp[i_] + v*vt[i,i_];
+                    }
+                }
+                
+                //
+                // update x:  x:=x+dx
+                //
+                for(i_=0; i_<=ncols-1;i_++)
+                {
+                    x[i_] = x[i_] + tmp[i_];
+                }
+            }
+            
+            //
+            // fill CX
+            //
+            if( rep.k>0 )
+            {
+                rep.cx = new double[ncols, rep.k];
+                for(i=0; i<=rep.k-1; i++)
+                {
+                    for(i_=0; i_<=ncols-1;i_++)
+                    {
+                        rep.cx[i_,i] = vt[kernelidx+i,i_];
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Internal LU solver
+
+          -- ALGLIB --
+             Copyright 27.01.2010 by Bochkanov Sergey
+        *************************************************************************/
+        private static void rmatrixlusolveinternal(double[,] lua,
+            int[] p,
+            double scalea,
+            int n,
+            double[,] a,
+            bool havea,
+            double[,] b,
+            int m,
+            ref int info,
+            densesolverreport rep,
+            ref double[,] x)
+        {
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int rfs = 0;
+            int nrfs = 0;
+            double[] xc = new double[0];
+            double[] y = new double[0];
+            double[] bc = new double[0];
+            double[] xa = new double[0];
+            double[] xb = new double[0];
+            double[] tx = new double[0];
+            double v = 0;
+            double verr = 0;
+            double mxb = 0;
+            double scaleright = 0;
+            bool smallerr = new bool();
+            bool terminatenexttime = new bool();
+            int i_ = 0;
+
+            info = 0;
+            x = new double[0,0];
+
+            alglib.ap.assert((double)(scalea)>(double)(0));
+            
+            //
+            // prepare: check inputs, allocate space...
+            //
+            if( n<=0 || m<=0 )
+            {
+                info = -1;
+                return;
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                if( p[i]>n-1 || p[i]<i )
+                {
+                    info = -1;
+                    return;
+                }
+            }
+            x = new double[n, m];
+            y = new double[n];
+            xc = new double[n];
+            bc = new double[n];
+            tx = new double[n+1];
+            xa = new double[n+1];
+            xb = new double[n+1];
+            
+            //
+            // estimate condition number, test for near singularity
+            //
+            rep.r1 = rcond.rmatrixlurcond1(lua, n);
+            rep.rinf = rcond.rmatrixlurcondinf(lua, n);
+            if( (double)(rep.r1)<(double)(rcond.rcondthreshold()) || (double)(rep.rinf)<(double)(rcond.rcondthreshold()) )
+            {
+                for(i=0; i<=n-1; i++)
+                {
+                    for(j=0; j<=m-1; j++)
+                    {
+                        x[i,j] = 0;
+                    }
+                }
+                rep.r1 = 0;
+                rep.rinf = 0;
+                info = -3;
+                return;
+            }
+            info = 1;
+            
+            //
+            // solve
+            //
+            for(k=0; k<=m-1; k++)
+            {
+                
+                //
+                // copy B to contiguous storage
+                //
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    bc[i_] = b[i_,k];
+                }
+                
+                //
+                // Scale right part:
+                // * MX stores max(|Bi|)
+                // * ScaleRight stores actual scaling applied to B when solving systems
+                //   it is chosen to make |scaleRight*b| close to 1.
+                //
+                mxb = 0;
+                for(i=0; i<=n-1; i++)
+                {
+                    mxb = Math.Max(mxb, Math.Abs(bc[i]));
+                }
+                if( (double)(mxb)==(double)(0) )
+                {
+                    mxb = 1;
+                }
+                scaleright = 1/mxb;
+                
+                //
+                // First, non-iterative part of solution process.
+                // We use separate code for this task because
+                // XDot is quite slow and we want to save time.
+                //
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    xc[i_] = scaleright*bc[i_];
+                }
+                rbasiclusolve(lua, p, scalea, n, ref xc, ref tx);
+                
+                //
+                // Iterative refinement of xc:
+                // * calculate r = bc-A*xc using extra-precise dot product
+                // * solve A*y = r
+                // * update x:=x+r
+                //
+                // This cycle is executed until one of two things happens:
+                // 1. maximum number of iterations reached
+                // 2. last iteration decreased error to the lower limit
+                //
+                if( havea )
+                {
+                    nrfs = densesolverrfsmax(n, rep.r1, rep.rinf);
+                    terminatenexttime = false;
+                    for(rfs=0; rfs<=nrfs-1; rfs++)
+                    {
+                        if( terminatenexttime )
+                        {
+                            break;
+                        }
+                        
+                        //
+                        // generate right part
+                        //
+                        smallerr = true;
+                        for(i_=0; i_<=n-1;i_++)
+                        {
+                            xb[i_] = xc[i_];
+                        }
+                        for(i=0; i<=n-1; i++)
+                        {
+                            for(i_=0; i_<=n-1;i_++)
+                            {
+                                xa[i_] = scalea*a[i,i_];
+                            }
+                            xa[n] = -1;
+                            xb[n] = scaleright*bc[i];
+                            xblas.xdot(xa, xb, n+1, ref tx, ref v, ref verr);
+                            y[i] = -v;
+                            smallerr = smallerr && (double)(Math.Abs(v))<(double)(4*verr);
+                        }
+                        if( smallerr )
+                        {
+                            terminatenexttime = true;
+                        }
+                        
+                        //
+                        // solve and update
+                        //
+                        rbasiclusolve(lua, p, scalea, n, ref y, ref tx);
+                        for(i_=0; i_<=n-1;i_++)
+                        {
+                            xc[i_] = xc[i_] + y[i_];
+                        }
+                    }
+                }
+                
+                //
+                // Store xc.
+                // Post-scale result.
+                //
+                v = scalea*mxb;
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    x[i_,k] = v*xc[i_];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Internal Cholesky solver
+
+          -- ALGLIB --
+             Copyright 27.01.2010 by Bochkanov Sergey
+        *************************************************************************/
+        private static void spdmatrixcholeskysolveinternal(double[,] cha,
+            double sqrtscalea,
+            int n,
+            bool isupper,
+            double[,] a,
+            bool havea,
+            double[,] b,
+            int m,
+            ref int info,
+            densesolverreport rep,
+            ref double[,] x)
+        {
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            double[] xc = new double[0];
+            double[] y = new double[0];
+            double[] bc = new double[0];
+            double[] xa = new double[0];
+            double[] xb = new double[0];
+            double[] tx = new double[0];
+            double v = 0;
+            double mxb = 0;
+            double scaleright = 0;
+            int i_ = 0;
+
+            info = 0;
+            x = new double[0,0];
+
+            alglib.ap.assert((double)(sqrtscalea)>(double)(0));
+            
+            //
+            // prepare: check inputs, allocate space...
+            //
+            if( n<=0 || m<=0 )
+            {
+                info = -1;
+                return;
+            }
+            x = new double[n, m];
+            y = new double[n];
+            xc = new double[n];
+            bc = new double[n];
+            tx = new double[n+1];
+            xa = new double[n+1];
+            xb = new double[n+1];
+            
+            //
+            // estimate condition number, test for near singularity
+            //
+            rep.r1 = rcond.spdmatrixcholeskyrcond(cha, n, isupper);
+            rep.rinf = rep.r1;
+            if( (double)(rep.r1)<(double)(rcond.rcondthreshold()) )
+            {
+                for(i=0; i<=n-1; i++)
+                {
+                    for(j=0; j<=m-1; j++)
+                    {
+                        x[i,j] = 0;
+                    }
+                }
+                rep.r1 = 0;
+                rep.rinf = 0;
+                info = -3;
+                return;
+            }
+            info = 1;
+            
+            //
+            // solve
+            //
+            for(k=0; k<=m-1; k++)
+            {
+                
+                //
+                // copy B to contiguous storage
+                //
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    bc[i_] = b[i_,k];
+                }
+                
+                //
+                // Scale right part:
+                // * MX stores max(|Bi|)
+                // * ScaleRight stores actual scaling applied to B when solving systems
+                //   it is chosen to make |scaleRight*b| close to 1.
+                //
+                mxb = 0;
+                for(i=0; i<=n-1; i++)
+                {
+                    mxb = Math.Max(mxb, Math.Abs(bc[i]));
+                }
+                if( (double)(mxb)==(double)(0) )
+                {
+                    mxb = 1;
+                }
+                scaleright = 1/mxb;
+                
+                //
+                // First, non-iterative part of solution process.
+                // We use separate code for this task because
+                // XDot is quite slow and we want to save time.
+                //
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    xc[i_] = scaleright*bc[i_];
+                }
+                spdbasiccholeskysolve(cha, sqrtscalea, n, isupper, ref xc, ref tx);
+                
+                //
+                // Store xc.
+                // Post-scale result.
+                //
+                v = math.sqr(sqrtscalea)*mxb;
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    x[i_,k] = v*xc[i_];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Internal LU solver
+
+          -- ALGLIB --
+             Copyright 27.01.2010 by Bochkanov Sergey
+        *************************************************************************/
+        private static void cmatrixlusolveinternal(complex[,] lua,
+            int[] p,
+            double scalea,
+            int n,
+            complex[,] a,
+            bool havea,
+            complex[,] b,
+            int m,
+            ref int info,
+            densesolverreport rep,
+            ref complex[,] x)
+        {
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int rfs = 0;
+            int nrfs = 0;
+            complex[] xc = new complex[0];
+            complex[] y = new complex[0];
+            complex[] bc = new complex[0];
+            complex[] xa = new complex[0];
+            complex[] xb = new complex[0];
+            complex[] tx = new complex[0];
+            double[] tmpbuf = new double[0];
+            complex v = 0;
+            double verr = 0;
+            double mxb = 0;
+            double scaleright = 0;
+            bool smallerr = new bool();
+            bool terminatenexttime = new bool();
+            int i_ = 0;
+
+            info = 0;
+            x = new complex[0,0];
+
+            alglib.ap.assert((double)(scalea)>(double)(0));
+            
+            //
+            // prepare: check inputs, allocate space...
+            //
+            if( n<=0 || m<=0 )
+            {
+                info = -1;
+                return;
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                if( p[i]>n-1 || p[i]<i )
+                {
+                    info = -1;
+                    return;
+                }
+            }
+            x = new complex[n, m];
+            y = new complex[n];
+            xc = new complex[n];
+            bc = new complex[n];
+            tx = new complex[n];
+            xa = new complex[n+1];
+            xb = new complex[n+1];
+            tmpbuf = new double[2*n+2];
+            
+            //
+            // estimate condition number, test for near singularity
+            //
+            rep.r1 = rcond.cmatrixlurcond1(lua, n);
+            rep.rinf = rcond.cmatrixlurcondinf(lua, n);
+            if( (double)(rep.r1)<(double)(rcond.rcondthreshold()) || (double)(rep.rinf)<(double)(rcond.rcondthreshold()) )
+            {
+                for(i=0; i<=n-1; i++)
+                {
+                    for(j=0; j<=m-1; j++)
+                    {
+                        x[i,j] = 0;
+                    }
+                }
+                rep.r1 = 0;
+                rep.rinf = 0;
+                info = -3;
+                return;
+            }
+            info = 1;
+            
+            //
+            // solve
+            //
+            for(k=0; k<=m-1; k++)
+            {
+                
+                //
+                // copy B to contiguous storage
+                //
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    bc[i_] = b[i_,k];
+                }
+                
+                //
+                // Scale right part:
+                // * MX stores max(|Bi|)
+                // * ScaleRight stores actual scaling applied to B when solving systems
+                //   it is chosen to make |scaleRight*b| close to 1.
+                //
+                mxb = 0;
+                for(i=0; i<=n-1; i++)
+                {
+                    mxb = Math.Max(mxb, math.abscomplex(bc[i]));
+                }
+                if( (double)(mxb)==(double)(0) )
+                {
+                    mxb = 1;
+                }
+                scaleright = 1/mxb;
+                
+                //
+                // First, non-iterative part of solution process.
+                // We use separate code for this task because
+                // XDot is quite slow and we want to save time.
+                //
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    xc[i_] = scaleright*bc[i_];
+                }
+                cbasiclusolve(lua, p, scalea, n, ref xc, ref tx);
+                
+                //
+                // Iterative refinement of xc:
+                // * calculate r = bc-A*xc using extra-precise dot product
+                // * solve A*y = r
+                // * update x:=x+r
+                //
+                // This cycle is executed until one of two things happens:
+                // 1. maximum number of iterations reached
+                // 2. last iteration decreased error to the lower limit
+                //
+                if( havea )
+                {
+                    nrfs = densesolverrfsmax(n, rep.r1, rep.rinf);
+                    terminatenexttime = false;
+                    for(rfs=0; rfs<=nrfs-1; rfs++)
+                    {
+                        if( terminatenexttime )
+                        {
+                            break;
+                        }
+                        
+                        //
+                        // generate right part
+                        //
+                        smallerr = true;
+                        for(i_=0; i_<=n-1;i_++)
+                        {
+                            xb[i_] = xc[i_];
+                        }
+                        for(i=0; i<=n-1; i++)
+                        {
+                            for(i_=0; i_<=n-1;i_++)
+                            {
+                                xa[i_] = scalea*a[i,i_];
+                            }
+                            xa[n] = -1;
+                            xb[n] = scaleright*bc[i];
+                            xblas.xcdot(xa, xb, n+1, ref tmpbuf, ref v, ref verr);
+                            y[i] = -v;
+                            smallerr = smallerr && (double)(math.abscomplex(v))<(double)(4*verr);
+                        }
+                        if( smallerr )
+                        {
+                            terminatenexttime = true;
+                        }
+                        
+                        //
+                        // solve and update
+                        //
+                        cbasiclusolve(lua, p, scalea, n, ref y, ref tx);
+                        for(i_=0; i_<=n-1;i_++)
+                        {
+                            xc[i_] = xc[i_] + y[i_];
+                        }
+                    }
+                }
+                
+                //
+                // Store xc.
+                // Post-scale result.
+                //
+                v = scalea*mxb;
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    x[i_,k] = v*xc[i_];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Internal Cholesky solver
+
+          -- ALGLIB --
+             Copyright 27.01.2010 by Bochkanov Sergey
+        *************************************************************************/
+        private static void hpdmatrixcholeskysolveinternal(complex[,] cha,
+            double sqrtscalea,
+            int n,
+            bool isupper,
+            complex[,] a,
+            bool havea,
+            complex[,] b,
+            int m,
+            ref int info,
+            densesolverreport rep,
+            ref complex[,] x)
+        {
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            complex[] xc = new complex[0];
+            complex[] y = new complex[0];
+            complex[] bc = new complex[0];
+            complex[] xa = new complex[0];
+            complex[] xb = new complex[0];
+            complex[] tx = new complex[0];
+            double v = 0;
+            double mxb = 0;
+            double scaleright = 0;
+            int i_ = 0;
+
+            info = 0;
+            x = new complex[0,0];
+
+            alglib.ap.assert((double)(sqrtscalea)>(double)(0));
+            
+            //
+            // prepare: check inputs, allocate space...
+            //
+            if( n<=0 || m<=0 )
+            {
+                info = -1;
+                return;
+            }
+            x = new complex[n, m];
+            y = new complex[n];
+            xc = new complex[n];
+            bc = new complex[n];
+            tx = new complex[n+1];
+            xa = new complex[n+1];
+            xb = new complex[n+1];
+            
+            //
+            // estimate condition number, test for near singularity
+            //
+            rep.r1 = rcond.hpdmatrixcholeskyrcond(cha, n, isupper);
+            rep.rinf = rep.r1;
+            if( (double)(rep.r1)<(double)(rcond.rcondthreshold()) )
+            {
+                for(i=0; i<=n-1; i++)
+                {
+                    for(j=0; j<=m-1; j++)
+                    {
+                        x[i,j] = 0;
+                    }
+                }
+                rep.r1 = 0;
+                rep.rinf = 0;
+                info = -3;
+                return;
+            }
+            info = 1;
+            
+            //
+            // solve
+            //
+            for(k=0; k<=m-1; k++)
+            {
+                
+                //
+                // copy B to contiguous storage
+                //
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    bc[i_] = b[i_,k];
+                }
+                
+                //
+                // Scale right part:
+                // * MX stores max(|Bi|)
+                // * ScaleRight stores actual scaling applied to B when solving systems
+                //   it is chosen to make |scaleRight*b| close to 1.
+                //
+                mxb = 0;
+                for(i=0; i<=n-1; i++)
+                {
+                    mxb = Math.Max(mxb, math.abscomplex(bc[i]));
+                }
+                if( (double)(mxb)==(double)(0) )
+                {
+                    mxb = 1;
+                }
+                scaleright = 1/mxb;
+                
+                //
+                // First, non-iterative part of solution process.
+                // We use separate code for this task because
+                // XDot is quite slow and we want to save time.
+                //
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    xc[i_] = scaleright*bc[i_];
+                }
+                hpdbasiccholeskysolve(cha, sqrtscalea, n, isupper, ref xc, ref tx);
+                
+                //
+                // Store xc.
+                // Post-scale result.
+                //
+                v = math.sqr(sqrtscalea)*mxb;
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    x[i_,k] = v*xc[i_];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Internal subroutine.
+        Returns maximum count of RFS iterations as function of:
+        1. machine epsilon
+        2. task size.
+        3. condition number
+
+          -- ALGLIB --
+             Copyright 27.01.2010 by Bochkanov Sergey
+        *************************************************************************/
+        private static int densesolverrfsmax(int n,
+            double r1,
+            double rinf)
+        {
+            int result = 0;
+
+            result = 5;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Internal subroutine.
+        Returns maximum count of RFS iterations as function of:
+        1. machine epsilon
+        2. task size.
+        3. norm-2 condition number
+
+          -- ALGLIB --
+             Copyright 27.01.2010 by Bochkanov Sergey
+        *************************************************************************/
+        private static int densesolverrfsmaxv2(int n,
+            double r2)
+        {
+            int result = 0;
+
+            result = densesolverrfsmax(n, 0, 0);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Basic LU solver for ScaleA*PLU*x = y.
+
+        This subroutine assumes that:
+        * L is well-scaled, and it is U which needs scaling by ScaleA.
+        * A=PLU is well-conditioned, so no zero divisions or overflow may occur
+
+          -- ALGLIB --
+             Copyright 27.01.2010 by Bochkanov Sergey
+        *************************************************************************/
+        private static void rbasiclusolve(double[,] lua,
+            int[] p,
+            double scalea,
+            int n,
+            ref double[] xb,
+            ref double[] tmp)
+        {
+            int i = 0;
+            double v = 0;
+            int i_ = 0;
+
+            for(i=0; i<=n-1; i++)
+            {
+                if( p[i]!=i )
+                {
+                    v = xb[i];
+                    xb[i] = xb[p[i]];
+                    xb[p[i]] = v;
+                }
+            }
+            for(i=1; i<=n-1; i++)
+            {
+                v = 0.0;
+                for(i_=0; i_<=i-1;i_++)
+                {
+                    v += lua[i,i_]*xb[i_];
+                }
+                xb[i] = xb[i]-v;
+            }
+            xb[n-1] = xb[n-1]/(scalea*lua[n-1,n-1]);
+            for(i=n-2; i>=0; i--)
+            {
+                for(i_=i+1; i_<=n-1;i_++)
+                {
+                    tmp[i_] = scalea*lua[i,i_];
+                }
+                v = 0.0;
+                for(i_=i+1; i_<=n-1;i_++)
+                {
+                    v += tmp[i_]*xb[i_];
+                }
+                xb[i] = (xb[i]-v)/(scalea*lua[i,i]);
+            }
+        }
+
+
+        /*************************************************************************
+        Basic Cholesky solver for ScaleA*Cholesky(A)'*x = y.
+
+        This subroutine assumes that:
+        * A*ScaleA is well scaled
+        * A is well-conditioned, so no zero divisions or overflow may occur
+
+          -- ALGLIB --
+             Copyright 27.01.2010 by Bochkanov Sergey
+        *************************************************************************/
+        private static void spdbasiccholeskysolve(double[,] cha,
+            double sqrtscalea,
+            int n,
+            bool isupper,
+            ref double[] xb,
+            ref double[] tmp)
+        {
+            int i = 0;
+            double v = 0;
+            int i_ = 0;
+
+            
+            //
+            // A = L*L' or A=U'*U
+            //
+            if( isupper )
+            {
+                
+                //
+                // Solve U'*y=b first.
+                //
+                for(i=0; i<=n-1; i++)
+                {
+                    xb[i] = xb[i]/(sqrtscalea*cha[i,i]);
+                    if( i<n-1 )
+                    {
+                        v = xb[i];
+                        for(i_=i+1; i_<=n-1;i_++)
+                        {
+                            tmp[i_] = sqrtscalea*cha[i,i_];
+                        }
+                        for(i_=i+1; i_<=n-1;i_++)
+                        {
+                            xb[i_] = xb[i_] - v*tmp[i_];
+                        }
+                    }
+                }
+                
+                //
+                // Solve U*x=y then.
+                //
+                for(i=n-1; i>=0; i--)
+                {
+                    if( i<n-1 )
+                    {
+                        for(i_=i+1; i_<=n-1;i_++)
+                        {
+                            tmp[i_] = sqrtscalea*cha[i,i_];
+                        }
+                        v = 0.0;
+                        for(i_=i+1; i_<=n-1;i_++)
+                        {
+                            v += tmp[i_]*xb[i_];
+                        }
+                        xb[i] = xb[i]-v;
+                    }
+                    xb[i] = xb[i]/(sqrtscalea*cha[i,i]);
+                }
+            }
+            else
+            {
+                
+                //
+                // Solve L*y=b first
+                //
+                for(i=0; i<=n-1; i++)
+                {
+                    if( i>0 )
+                    {
+                        for(i_=0; i_<=i-1;i_++)
+                        {
+                            tmp[i_] = sqrtscalea*cha[i,i_];
+                        }
+                        v = 0.0;
+                        for(i_=0; i_<=i-1;i_++)
+                        {
+                            v += tmp[i_]*xb[i_];
+                        }
+                        xb[i] = xb[i]-v;
+                    }
+                    xb[i] = xb[i]/(sqrtscalea*cha[i,i]);
+                }
+                
+                //
+                // Solve L'*x=y then.
+                //
+                for(i=n-1; i>=0; i--)
+                {
+                    xb[i] = xb[i]/(sqrtscalea*cha[i,i]);
+                    if( i>0 )
+                    {
+                        v = xb[i];
+                        for(i_=0; i_<=i-1;i_++)
+                        {
+                            tmp[i_] = sqrtscalea*cha[i,i_];
+                        }
+                        for(i_=0; i_<=i-1;i_++)
+                        {
+                            xb[i_] = xb[i_] - v*tmp[i_];
+                        }
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Basic LU solver for ScaleA*PLU*x = y.
+
+        This subroutine assumes that:
+        * L is well-scaled, and it is U which needs scaling by ScaleA.
+        * A=PLU is well-conditioned, so no zero divisions or overflow may occur
+
+          -- ALGLIB --
+             Copyright 27.01.2010 by Bochkanov Sergey
+        *************************************************************************/
+        private static void cbasiclusolve(complex[,] lua,
+            int[] p,
+            double scalea,
+            int n,
+            ref complex[] xb,
+            ref complex[] tmp)
+        {
+            int i = 0;
+            complex v = 0;
+            int i_ = 0;
+
+            for(i=0; i<=n-1; i++)
+            {
+                if( p[i]!=i )
+                {
+                    v = xb[i];
+                    xb[i] = xb[p[i]];
+                    xb[p[i]] = v;
+                }
+            }
+            for(i=1; i<=n-1; i++)
+            {
+                v = 0.0;
+                for(i_=0; i_<=i-1;i_++)
+                {
+                    v += lua[i,i_]*xb[i_];
+                }
+                xb[i] = xb[i]-v;
+            }
+            xb[n-1] = xb[n-1]/(scalea*lua[n-1,n-1]);
+            for(i=n-2; i>=0; i--)
+            {
+                for(i_=i+1; i_<=n-1;i_++)
+                {
+                    tmp[i_] = scalea*lua[i,i_];
+                }
+                v = 0.0;
+                for(i_=i+1; i_<=n-1;i_++)
+                {
+                    v += tmp[i_]*xb[i_];
+                }
+                xb[i] = (xb[i]-v)/(scalea*lua[i,i]);
+            }
+        }
+
+
+        /*************************************************************************
+        Basic Cholesky solver for ScaleA*Cholesky(A)'*x = y.
+
+        This subroutine assumes that:
+        * A*ScaleA is well scaled
+        * A is well-conditioned, so no zero divisions or overflow may occur
+
+          -- ALGLIB --
+             Copyright 27.01.2010 by Bochkanov Sergey
+        *************************************************************************/
+        private static void hpdbasiccholeskysolve(complex[,] cha,
+            double sqrtscalea,
+            int n,
+            bool isupper,
+            ref complex[] xb,
+            ref complex[] tmp)
+        {
+            int i = 0;
+            complex v = 0;
+            int i_ = 0;
+
+            
+            //
+            // A = L*L' or A=U'*U
+            //
+            if( isupper )
+            {
+                
+                //
+                // Solve U'*y=b first.
+                //
+                for(i=0; i<=n-1; i++)
+                {
+                    xb[i] = xb[i]/(sqrtscalea*math.conj(cha[i,i]));
+                    if( i<n-1 )
+                    {
+                        v = xb[i];
+                        for(i_=i+1; i_<=n-1;i_++)
+                        {
+                            tmp[i_] = sqrtscalea*math.conj(cha[i,i_]);
+                        }
+                        for(i_=i+1; i_<=n-1;i_++)
+                        {
+                            xb[i_] = xb[i_] - v*tmp[i_];
+                        }
+                    }
+                }
+                
+                //
+                // Solve U*x=y then.
+                //
+                for(i=n-1; i>=0; i--)
+                {
+                    if( i<n-1 )
+                    {
+                        for(i_=i+1; i_<=n-1;i_++)
+                        {
+                            tmp[i_] = sqrtscalea*cha[i,i_];
+                        }
+                        v = 0.0;
+                        for(i_=i+1; i_<=n-1;i_++)
+                        {
+                            v += tmp[i_]*xb[i_];
+                        }
+                        xb[i] = xb[i]-v;
+                    }
+                    xb[i] = xb[i]/(sqrtscalea*cha[i,i]);
+                }
+            }
+            else
+            {
+                
+                //
+                // Solve L*y=b first
+                //
+                for(i=0; i<=n-1; i++)
+                {
+                    if( i>0 )
+                    {
+                        for(i_=0; i_<=i-1;i_++)
+                        {
+                            tmp[i_] = sqrtscalea*cha[i,i_];
+                        }
+                        v = 0.0;
+                        for(i_=0; i_<=i-1;i_++)
+                        {
+                            v += tmp[i_]*xb[i_];
+                        }
+                        xb[i] = xb[i]-v;
+                    }
+                    xb[i] = xb[i]/(sqrtscalea*cha[i,i]);
+                }
+                
+                //
+                // Solve L'*x=y then.
+                //
+                for(i=n-1; i>=0; i--)
+                {
+                    xb[i] = xb[i]/(sqrtscalea*math.conj(cha[i,i]));
+                    if( i>0 )
+                    {
+                        v = xb[i];
+                        for(i_=0; i_<=i-1;i_++)
+                        {
+                            tmp[i_] = sqrtscalea*math.conj(cha[i,i_]);
+                        }
+                        for(i_=0; i_<=i-1;i_++)
+                        {
+                            xb[i_] = xb[i_] - v*tmp[i_];
+                        }
+                    }
+                }
+            }
+        }
+
+
+    }
+    public class linlsqr
+    {
+        /*************************************************************************
+        This object stores state of the LinLSQR method.
+
+        You should use ALGLIB functions to work with this object.
+        *************************************************************************/
+        public class linlsqrstate
+        {
+            public normestimator.normestimatorstate nes;
+            public double[] rx;
+            public double[] b;
+            public int n;
+            public int m;
+            public double[] ui;
+            public double[] uip1;
+            public double[] vi;
+            public double[] vip1;
+            public double[] omegai;
+            public double[] omegaip1;
+            public double alphai;
+            public double alphaip1;
+            public double betai;
+            public double betaip1;
+            public double phibari;
+            public double phibarip1;
+            public double phii;
+            public double rhobari;
+            public double rhobarip1;
+            public double rhoi;
+            public double ci;
+            public double si;
+            public double theta;
+            public double lambdai;
+            public double[] d;
+            public double anorm;
+            public double bnorm2;
+            public double dnorm;
+            public double r2;
+            public double[] x;
+            public double[] mv;
+            public double[] mtv;
+            public double epsa;
+            public double epsb;
+            public double epsc;
+            public int maxits;
+            public bool xrep;
+            public bool xupdated;
+            public bool needmv;
+            public bool needmtv;
+            public bool needmv2;
+            public bool needvmv;
+            public bool needprec;
+            public int repiterationscount;
+            public int repnmv;
+            public int repterminationtype;
+            public bool running;
+            public rcommstate rstate;
+            public linlsqrstate()
+            {
+                nes = new normestimator.normestimatorstate();
+                rx = new double[0];
+                b = new double[0];
+                ui = new double[0];
+                uip1 = new double[0];
+                vi = new double[0];
+                vip1 = new double[0];
+                omegai = new double[0];
+                omegaip1 = new double[0];
+                d = new double[0];
+                x = new double[0];
+                mv = new double[0];
+                mtv = new double[0];
+                rstate = new rcommstate();
+            }
+        };
+
+
+        public class linlsqrreport
+        {
+            public int iterationscount;
+            public int nmv;
+            public int terminationtype;
+        };
+
+
+
+
+        public const double atol = 1.0E-6;
+        public const double btol = 1.0E-6;
+
+
+        /*************************************************************************
+        This function initializes linear LSQR Solver. This solver is used to solve
+        non-symmetric (and, possibly, non-square) problems. Least squares solution
+        is returned for non-compatible systems.
+
+        USAGE:
+        1. User initializes algorithm state with LinLSQRCreate() call
+        2. User tunes solver parameters with  LinLSQRSetCond() and other functions
+        3. User  calls  LinLSQRSolveSparse()  function which takes algorithm state 
+           and SparseMatrix object.
+        4. User calls LinLSQRResults() to get solution
+        5. Optionally, user may call LinLSQRSolveSparse() again to  solve  another  
+           problem  with different matrix and/or right part without reinitializing 
+           LinLSQRState structure.
+          
+        INPUT PARAMETERS:
+            M       -   number of rows in A
+            N       -   number of variables, N>0
+
+        OUTPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+
+          -- ALGLIB --
+             Copyright 30.11.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void linlsqrcreate(int m,
+            int n,
+            linlsqrstate state)
+        {
+            int i = 0;
+
+            alglib.ap.assert(m>0, "LinLSQRCreate: M<=0");
+            alglib.ap.assert(n>0, "LinLSQRCreate: N<=0");
+            state.m = m;
+            state.n = n;
+            state.epsa = atol;
+            state.epsb = btol;
+            state.epsc = 1/Math.Sqrt(math.machineepsilon);
+            state.maxits = 0;
+            state.lambdai = 0;
+            state.xrep = false;
+            state.running = false;
+            
+            //
+            // * allocate arrays
+            // * set RX to NAN (just for the case user calls Results() without 
+            //   calling SolveSparse()
+            // * set B to zero
+            //
+            normestimator.normestimatorcreate(m, n, 2, 2, state.nes);
+            state.rx = new double[state.n];
+            state.ui = new double[state.m+state.n];
+            state.uip1 = new double[state.m+state.n];
+            state.vip1 = new double[state.n];
+            state.vi = new double[state.n];
+            state.omegai = new double[state.n];
+            state.omegaip1 = new double[state.n];
+            state.d = new double[state.n];
+            state.x = new double[state.m+state.n];
+            state.mv = new double[state.m+state.n];
+            state.mtv = new double[state.n];
+            state.b = new double[state.m];
+            for(i=0; i<=n-1; i++)
+            {
+                state.rx[i] = Double.NaN;
+            }
+            for(i=0; i<=m-1; i++)
+            {
+                state.b[i] = 0;
+            }
+            state.rstate.ia = new int[1+1];
+            state.rstate.ra = new double[0+1];
+            state.rstate.stage = -1;
+        }
+
+
+        /*************************************************************************
+        This function sets right part. By default, right part is zero.
+
+        INPUT PARAMETERS:
+            B       -   right part, array[N].
+
+        OUTPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+
+          -- ALGLIB --
+             Copyright 30.11.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void linlsqrsetb(linlsqrstate state,
+            double[] b)
+        {
+            int i = 0;
+
+            alglib.ap.assert(!state.running, "LinLSQRSetB: you can not change B when LinLSQRIteration is running");
+            alglib.ap.assert(state.m<=alglib.ap.len(b), "LinLSQRSetB: Length(B)<M");
+            alglib.ap.assert(apserv.isfinitevector(b, state.m), "LinLSQRSetB: B contains infinite or NaN values");
+            state.bnorm2 = 0;
+            for(i=0; i<=state.m-1; i++)
+            {
+                state.b[i] = b[i];
+                state.bnorm2 = state.bnorm2+b[i]*b[i];
+            }
+        }
+
+
+        /*************************************************************************
+        This function sets optional Tikhonov regularization coefficient.
+        It is zero by default.
+
+        INPUT PARAMETERS:
+            LambdaI -   regularization factor, LambdaI>=0
+
+        OUTPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+            
+          -- ALGLIB --
+             Copyright 30.11.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void linlsqrsetlambdai(linlsqrstate state,
+            double lambdai)
+        {
+            alglib.ap.assert(!state.running, "LinLSQRSetLambdaI: you can not set LambdaI, because function LinLSQRIteration is running");
+            alglib.ap.assert(math.isfinite(lambdai) && (double)(lambdai)>=(double)(0), "LinLSQRSetLambdaI: LambdaI is infinite or NaN");
+            state.lambdai = lambdai;
+        }
+
+
+        /*************************************************************************
+
+          -- ALGLIB --
+             Copyright 30.11.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static bool linlsqriteration(linlsqrstate state)
+        {
+            bool result = new bool();
+            int summn = 0;
+            double bnorm = 0;
+            int i = 0;
+            int i_ = 0;
+
+            
+            //
+            // Reverse communication preparations
+            // I know it looks ugly, but it works the same way
+            // anywhere from C++ to Python.
+            //
+            // This code initializes locals by:
+            // * random values determined during code
+            //   generation - on first subroutine call
+            // * values from previous call - on subsequent calls
+            //
+            if( state.rstate.stage>=0 )
+            {
+                summn = state.rstate.ia[0];
+                i = state.rstate.ia[1];
+                bnorm = state.rstate.ra[0];
+            }
+            else
+            {
+                summn = -983;
+                i = -989;
+                bnorm = -834;
+            }
+            if( state.rstate.stage==0 )
+            {
+                goto lbl_0;
+            }
+            if( state.rstate.stage==1 )
+            {
+                goto lbl_1;
+            }
+            if( state.rstate.stage==2 )
+            {
+                goto lbl_2;
+            }
+            if( state.rstate.stage==3 )
+            {
+                goto lbl_3;
+            }
+            if( state.rstate.stage==4 )
+            {
+                goto lbl_4;
+            }
+            if( state.rstate.stage==5 )
+            {
+                goto lbl_5;
+            }
+            if( state.rstate.stage==6 )
+            {
+                goto lbl_6;
+            }
+            
+            //
+            // Routine body
+            //
+            alglib.ap.assert(alglib.ap.len(state.b)>0, "LinLSQRIteration: using non-allocated array B");
+            bnorm = Math.Sqrt(state.bnorm2);
+            state.running = true;
+            state.repnmv = 0;
+            clearrfields(state);
+            state.repiterationscount = 0;
+            summn = state.m+state.n;
+            state.r2 = state.bnorm2;
+            
+            //
+            //estimate for ANorm
+            //
+            normestimator.normestimatorrestart(state.nes);
+        lbl_7:
+            if( !normestimator.normestimatoriteration(state.nes) )
+            {
+                goto lbl_8;
+            }
+            if( !state.nes.needmv )
+            {
+                goto lbl_9;
+            }
+            for(i_=0; i_<=state.n-1;i_++)
+            {
+                state.x[i_] = state.nes.x[i_];
+            }
+            state.repnmv = state.repnmv+1;
+            clearrfields(state);
+            state.needmv = true;
+            state.rstate.stage = 0;
+            goto lbl_rcomm;
+        lbl_0:
+            state.needmv = false;
+            for(i_=0; i_<=state.m-1;i_++)
+            {
+                state.nes.mv[i_] = state.mv[i_];
+            }
+            goto lbl_7;
+        lbl_9:
+            if( !state.nes.needmtv )
+            {
+                goto lbl_11;
+            }
+            for(i_=0; i_<=state.m-1;i_++)
+            {
+                state.x[i_] = state.nes.x[i_];
+            }
+            
+            //
+            //matrix-vector multiplication
+            //
+            state.repnmv = state.repnmv+1;
+            clearrfields(state);
+            state.needmtv = true;
+            state.rstate.stage = 1;
+            goto lbl_rcomm;
+        lbl_1:
+            state.needmtv = false;
+            for(i_=0; i_<=state.n-1;i_++)
+            {
+                state.nes.mtv[i_] = state.mtv[i_];
+            }
+            goto lbl_7;
+        lbl_11:
+            goto lbl_7;
+        lbl_8:
+            normestimator.normestimatorresults(state.nes, ref state.anorm);
+            
+            //
+            //initialize .RX by zeros
+            //
+            for(i=0; i<=state.n-1; i++)
+            {
+                state.rx[i] = 0;
+            }
+            
+            //
+            //output first report
+            //
+            if( !state.xrep )
+            {
+                goto lbl_13;
+            }
+            for(i_=0; i_<=state.n-1;i_++)
+            {
+                state.x[i_] = state.rx[i_];
+            }
+            clearrfields(state);
+            state.xupdated = true;
+            state.rstate.stage = 2;
+            goto lbl_rcomm;
+        lbl_2:
+            state.xupdated = false;
+        lbl_13:
+            
+            //
+            // LSQR, Step 0.
+            //
+            // Algorithm outline corresponds to one which was described at p.50 of
+            // "LSQR - an algorithm for sparse linear equations and sparse least 
+            // squares" by C.Paige and M.Saunders with one small addition - we
+            // explicitly extend system matrix by additional N lines in order 
+            // to handle non-zero lambda, i.e. original A is replaced by
+            //         [ A        ]
+            // A_mod = [          ]
+            //         [ lambda*I ].
+            //
+            // Step 0:
+            //     x[0]          = 0
+            //     beta[1]*u[1]  = b
+            //     alpha[1]*v[1] = A_mod'*u[1]
+            //     w[1]          = v[1]
+            //     phiBar[1]     = beta[1]
+            //     rhoBar[1]     = alpha[1]
+            //     d[0]          = 0
+            //
+            // NOTE:
+            // There are three criteria for stopping:
+            // (S0) maximum number of iterations
+            // (S1) ||Rk||<=EpsB*||B||;
+            // (S2) ||A^T*Rk||/(||A||*||Rk||)<=EpsA.
+            // It is very important that S2 always checked AFTER S1. It is necessary
+            // to avoid division by zero when Rk=0.
+            //
+            state.betai = bnorm;
+            if( (double)(state.betai)==(double)(0) )
+            {
+                
+                //
+                // Zero right part
+                //
+                state.running = false;
+                state.repterminationtype = 1;
+                result = false;
+                return result;
+            }
+            for(i=0; i<=summn-1; i++)
+            {
+                if( i<state.m )
+                {
+                    state.ui[i] = state.b[i]/state.betai;
+                }
+                else
+                {
+                    state.ui[i] = 0;
+                }
+                state.x[i] = state.ui[i];
+            }
+            state.repnmv = state.repnmv+1;
+            clearrfields(state);
+            state.needmtv = true;
+            state.rstate.stage = 3;
+            goto lbl_rcomm;
+        lbl_3:
+            state.needmtv = false;
+            for(i=0; i<=state.n-1; i++)
+            {
+                state.mtv[i] = state.mtv[i]+state.lambdai*state.ui[state.m+i];
+            }
+            state.alphai = 0;
+            for(i=0; i<=state.n-1; i++)
+            {
+                state.alphai = state.alphai+state.mtv[i]*state.mtv[i];
+            }
+            state.alphai = Math.Sqrt(state.alphai);
+            if( (double)(state.alphai)==(double)(0) )
+            {
+                
+                //
+                // Orthogonality stopping criterion is met
+                //
+                state.running = false;
+                state.repterminationtype = 4;
+                result = false;
+                return result;
+            }
+            for(i=0; i<=state.n-1; i++)
+            {
+                state.vi[i] = state.mtv[i]/state.alphai;
+                state.omegai[i] = state.vi[i];
+            }
+            state.phibari = state.betai;
+            state.rhobari = state.alphai;
+            for(i=0; i<=state.n-1; i++)
+            {
+                state.d[i] = 0;
+            }
+            state.dnorm = 0;
+            
+            //
+            // Steps I=1, 2, ...
+            //
+        lbl_15:
+            if( false )
+            {
+                goto lbl_16;
+            }
+            
+            //
+            // At I-th step State.RepIterationsCount=I.
+            //
+            state.repiterationscount = state.repiterationscount+1;
+            
+            //
+            // Bidiagonalization part:
+            //     beta[i+1]*u[i+1]  = A_mod*v[i]-alpha[i]*u[i]
+            //     alpha[i+1]*v[i+1] = A_mod'*u[i+1] - beta[i+1]*v[i]
+            //     
+            // NOTE:  beta[i+1]=0 or alpha[i+1]=0 will lead to successful termination
+            //        in the end of the current iteration. In this case u/v are zero.
+            // NOTE2: algorithm won't fail on zero alpha or beta (there will be no
+            //        division by zero because it will be stopped BEFORE division
+            //        occurs). However, near-zero alpha and beta won't stop algorithm
+            //        and, although no division by zero will happen, orthogonality 
+            //        in U and V will be lost.
+            //
+            for(i_=0; i_<=state.n-1;i_++)
+            {
+                state.x[i_] = state.vi[i_];
+            }
+            state.repnmv = state.repnmv+1;
+            clearrfields(state);
+            state.needmv = true;
+            state.rstate.stage = 4;
+            goto lbl_rcomm;
+        lbl_4:
+            state.needmv = false;
+            for(i=0; i<=state.n-1; i++)
+            {
+                state.mv[state.m+i] = state.lambdai*state.vi[i];
+            }
+            state.betaip1 = 0;
+            for(i=0; i<=summn-1; i++)
+            {
+                state.uip1[i] = state.mv[i]-state.alphai*state.ui[i];
+                state.betaip1 = state.betaip1+state.uip1[i]*state.uip1[i];
+            }
+            if( (double)(state.betaip1)!=(double)(0) )
+            {
+                state.betaip1 = Math.Sqrt(state.betaip1);
+                for(i=0; i<=summn-1; i++)
+                {
+                    state.uip1[i] = state.uip1[i]/state.betaip1;
+                }
+            }
+            for(i_=0; i_<=state.m-1;i_++)
+            {
+                state.x[i_] = state.uip1[i_];
+            }
+            state.repnmv = state.repnmv+1;
+            clearrfields(state);
+            state.needmtv = true;
+            state.rstate.stage = 5;
+            goto lbl_rcomm;
+        lbl_5:
+            state.needmtv = false;
+            for(i=0; i<=state.n-1; i++)
+            {
+                state.mtv[i] = state.mtv[i]+state.lambdai*state.uip1[state.m+i];
+            }
+            state.alphaip1 = 0;
+            for(i=0; i<=state.n-1; i++)
+            {
+                state.vip1[i] = state.mtv[i]-state.betaip1*state.vi[i];
+                state.alphaip1 = state.alphaip1+state.vip1[i]*state.vip1[i];
+            }
+            if( (double)(state.alphaip1)!=(double)(0) )
+            {
+                state.alphaip1 = Math.Sqrt(state.alphaip1);
+                for(i=0; i<=state.n-1; i++)
+                {
+                    state.vip1[i] = state.vip1[i]/state.alphaip1;
+                }
+            }
+            
+            //
+            // Build next orthogonal transformation
+            //
+            state.rhoi = apserv.safepythag2(state.rhobari, state.betaip1);
+            state.ci = state.rhobari/state.rhoi;
+            state.si = state.betaip1/state.rhoi;
+            state.theta = state.si*state.alphaip1;
+            state.rhobarip1 = -(state.ci*state.alphaip1);
+            state.phii = state.ci*state.phibari;
+            state.phibarip1 = state.si*state.phibari;
+            
+            //
+            // Update .RNorm
+            //
+            // This tricky  formula  is  necessary  because  simply  writing
+            // State.R2:=State.PhiBarIP1*State.PhiBarIP1 does NOT guarantees
+            // monotonic decrease of R2. Roundoff error combined with 80-bit
+            // precision used internally by Intel chips allows R2 to increase
+            // slightly in some rare, but possible cases. This property is
+            // undesirable, so we prefer to guard against R increase.
+            //
+            state.r2 = Math.Min(state.r2, state.phibarip1*state.phibarip1);
+            
+            //
+            // Update d and DNorm, check condition-related stopping criteria
+            //
+            for(i=0; i<=state.n-1; i++)
+            {
+                state.d[i] = 1/state.rhoi*(state.vi[i]-state.theta*state.d[i]);
+                state.dnorm = state.dnorm+state.d[i]*state.d[i];
+            }
+            if( (double)(Math.Sqrt(state.dnorm)*state.anorm)>=(double)(state.epsc) )
+            {
+                state.running = false;
+                state.repterminationtype = 7;
+                result = false;
+                return result;
+            }
+            
+            //
+            // Update x, output report
+            //
+            for(i=0; i<=state.n-1; i++)
+            {
+                state.rx[i] = state.rx[i]+state.phii/state.rhoi*state.omegai[i];
+            }
+            if( !state.xrep )
+            {
+                goto lbl_17;
+            }
+            for(i_=0; i_<=state.n-1;i_++)
+            {
+                state.x[i_] = state.rx[i_];
+            }
+            clearrfields(state);
+            state.xupdated = true;
+            state.rstate.stage = 6;
+            goto lbl_rcomm;
+        lbl_6:
+            state.xupdated = false;
+        lbl_17:
+            
+            //
+            // Check stopping criteria
+            // 1. achieved required number of iterations;
+            // 2. ||Rk||<=EpsB*||B||;
+            // 3. ||A^T*Rk||/(||A||*||Rk||)<=EpsA;
+            //
+            if( state.maxits>0 && state.repiterationscount>=state.maxits )
+            {
+                
+                //
+                // Achieved required number of iterations
+                //
+                state.running = false;
+                state.repterminationtype = 5;
+                result = false;
+                return result;
+            }
+            if( (double)(state.phibarip1)<=(double)(state.epsb*bnorm) )
+            {
+                
+                //
+                // ||Rk||<=EpsB*||B||, here ||Rk||=PhiBar
+                //
+                state.running = false;
+                state.repterminationtype = 1;
+                result = false;
+                return result;
+            }
+            if( (double)(state.alphaip1*Math.Abs(state.ci)/state.anorm)<=(double)(state.epsa) )
+            {
+                
+                //
+                // ||A^T*Rk||/(||A||*||Rk||)<=EpsA, here ||A^T*Rk||=PhiBar*Alpha[i+1]*|.C|
+                //
+                state.running = false;
+                state.repterminationtype = 4;
+                result = false;
+                return result;
+            }
+            
+            //
+            // Update omega
+            //
+            for(i=0; i<=state.n-1; i++)
+            {
+                state.omegaip1[i] = state.vip1[i]-state.theta/state.rhoi*state.omegai[i];
+            }
+            
+            //
+            // Prepare for the next iteration - rename variables:
+            // u[i]   := u[i+1]
+            // v[i]   := v[i+1]
+            // rho[i] := rho[i+1]
+            // ...
+            //
+            for(i_=0; i_<=summn-1;i_++)
+            {
+                state.ui[i_] = state.uip1[i_];
+            }
+            for(i_=0; i_<=state.n-1;i_++)
+            {
+                state.vi[i_] = state.vip1[i_];
+            }
+            for(i_=0; i_<=state.n-1;i_++)
+            {
+                state.omegai[i_] = state.omegaip1[i_];
+            }
+            state.alphai = state.alphaip1;
+            state.betai = state.betaip1;
+            state.phibari = state.phibarip1;
+            state.rhobari = state.rhobarip1;
+            goto lbl_15;
+        lbl_16:
+            result = false;
+            return result;
+            
+            //
+            // Saving state
+            //
+        lbl_rcomm:
+            result = true;
+            state.rstate.ia[0] = summn;
+            state.rstate.ia[1] = i;
+            state.rstate.ra[0] = bnorm;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Procedure for solution of A*x=b with sparse A.
+
+        INPUT PARAMETERS:
+            State   -   algorithm state
+            A       -   sparse M*N matrix in the CRS format (you MUST contvert  it 
+                        to CRS format  by  calling  SparseConvertToCRS()  function
+                        BEFORE you pass it to this function).
+            B       -   right part, array[M]
+
+        RESULT:
+            This function returns no result.
+            You can get solution by calling LinCGResults()
+
+          -- ALGLIB --
+             Copyright 30.11.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void linlsqrsolvesparse(linlsqrstate state,
+            sparse.sparsematrix a,
+            double[] b)
+        {
+            alglib.ap.assert(!state.running, "LinLSQRSolveSparse: you can not call this function when LinLSQRIteration is running");
+            alglib.ap.assert(alglib.ap.len(b)>=state.m, "LinLSQRSolveSparse: Length(B)<M");
+            alglib.ap.assert(apserv.isfinitevector(b, state.m), "LinLSQRSolveSparse: B contains infinite or NaN values");
+            linlsqrsetb(state, b);
+            linlsqrrestart(state);
+            while( linlsqriteration(state) )
+            {
+                if( state.needmv )
+                {
+                    sparse.sparsemv(a, state.x, ref state.mv);
+                }
+                if( state.needmtv )
+                {
+                    sparse.sparsemtv(a, state.x, ref state.mtv);
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This function sets stopping criteria.
+
+        INPUT PARAMETERS:
+            EpsA    -   algorithm will be stopped if ||A^T*Rk||/(||A||*||Rk||)<=EpsA.
+            EpsB    -   algorithm will be stopped if ||Rk||<=EpsB*||B||
+            MaxIts  -   algorithm will be stopped if number of iterations
+                        more than MaxIts.
+
+        OUTPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+
+        NOTE: if EpsA,EpsB,EpsC and MaxIts are zero then these variables will
+        be setted as default values.
+            
+          -- ALGLIB --
+             Copyright 30.11.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void linlsqrsetcond(linlsqrstate state,
+            double epsa,
+            double epsb,
+            int maxits)
+        {
+            alglib.ap.assert(!state.running, "LinLSQRSetCond: you can not call this function when LinLSQRIteration is running");
+            alglib.ap.assert(math.isfinite(epsa) && (double)(epsa)>=(double)(0), "LinLSQRSetCond: EpsA is negative, INF or NAN");
+            alglib.ap.assert(math.isfinite(epsb) && (double)(epsb)>=(double)(0), "LinLSQRSetCond: EpsB is negative, INF or NAN");
+            alglib.ap.assert(maxits>=0, "LinLSQRSetCond: MaxIts is negative");
+            if( ((double)(epsa)==(double)(0) && (double)(epsb)==(double)(0)) && maxits==0 )
+            {
+                state.epsa = atol;
+                state.epsb = btol;
+                state.maxits = state.n;
+            }
+            else
+            {
+                state.epsa = epsa;
+                state.epsb = epsb;
+                state.maxits = maxits;
+            }
+        }
+
+
+        /*************************************************************************
+        LSQR solver: results.
+
+        This function must be called after LinLSQRSolve
+
+        INPUT PARAMETERS:
+            State   -   algorithm state
+
+        OUTPUT PARAMETERS:
+            X       -   array[N], solution
+            Rep     -   optimization report:
+                        * Rep.TerminationType completetion code:
+                            *  1    ||Rk||<=EpsB*||B||
+                            *  4    ||A^T*Rk||/(||A||*||Rk||)<=EpsA
+                            *  5    MaxIts steps was taken
+                            *  7    rounding errors prevent further progress,
+                                    X contains best point found so far.
+                                    (sometimes returned on singular systems)
+                        * Rep.IterationsCount contains iterations count
+                        * NMV countains number of matrix-vector calculations
+                        
+          -- ALGLIB --
+             Copyright 30.11.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void linlsqrresults(linlsqrstate state,
+            ref double[] x,
+            linlsqrreport rep)
+        {
+            int i_ = 0;
+
+            x = new double[0];
+
+            alglib.ap.assert(!state.running, "LinLSQRResult: you can not call this function when LinLSQRIteration is running");
+            if( alglib.ap.len(x)<state.n )
+            {
+                x = new double[state.n];
+            }
+            for(i_=0; i_<=state.n-1;i_++)
+            {
+                x[i_] = state.rx[i_];
+            }
+            rep.iterationscount = state.repiterationscount;
+            rep.nmv = state.repnmv;
+            rep.terminationtype = state.repterminationtype;
+        }
+
+
+        /*************************************************************************
+        This function turns on/off reporting.
+
+        INPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+            NeedXRep-   whether iteration reports are needed or not
+
+        If NeedXRep is True, algorithm will call rep() callback function if  it is
+        provided to MinCGOptimize().
+
+          -- ALGLIB --
+             Copyright 30.11.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void linlsqrsetxrep(linlsqrstate state,
+            bool needxrep)
+        {
+            state.xrep = needxrep;
+        }
+
+
+        /*************************************************************************
+        This function restarts LinLSQRIteration
+
+          -- ALGLIB --
+             Copyright 30.11.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void linlsqrrestart(linlsqrstate state)
+        {
+            state.rstate.ia = new int[1+1];
+            state.rstate.ra = new double[0+1];
+            state.rstate.stage = -1;
+            clearrfields(state);
+        }
+
+
+        /*************************************************************************
+        Clears request fileds (to be sure that we don't forgot to clear something)
+        *************************************************************************/
+        private static void clearrfields(linlsqrstate state)
+        {
+            state.xupdated = false;
+            state.needmv = false;
+            state.needmtv = false;
+            state.needmv2 = false;
+            state.needvmv = false;
+            state.needprec = false;
+        }
+
+
+    }
+    public class lincg
+    {
+        /*************************************************************************
+        This object stores state of the linear CG method.
+
+        You should use ALGLIB functions to work with this object.
+        Never try to access its fields directly!
+        *************************************************************************/
+        public class lincgstate
+        {
+            public double[] rx;
+            public double[] b;
+            public int n;
+            public double[] cx;
+            public double[] cr;
+            public double[] cz;
+            public double[] p;
+            public double[] r;
+            public double[] z;
+            public double alpha;
+            public double beta;
+            public double r2;
+            public double meritfunction;
+            public double[] x;
+            public double[] mv;
+            public double[] pv;
+            public double vmv;
+            public double[] startx;
+            public double epsf;
+            public int maxits;
+            public int itsbeforerestart;
+            public int itsbeforerupdate;
+            public bool xrep;
+            public bool xupdated;
+            public bool needmv;
+            public bool needmtv;
+            public bool needmv2;
+            public bool needvmv;
+            public bool needprec;
+            public int repiterationscount;
+            public int repnmv;
+            public int repterminationtype;
+            public bool running;
+            public rcommstate rstate;
+            public lincgstate()
+            {
+                rx = new double[0];
+                b = new double[0];
+                cx = new double[0];
+                cr = new double[0];
+                cz = new double[0];
+                p = new double[0];
+                r = new double[0];
+                z = new double[0];
+                x = new double[0];
+                mv = new double[0];
+                pv = new double[0];
+                startx = new double[0];
+                rstate = new rcommstate();
+            }
+        };
+
+
+        public class lincgreport
+        {
+            public int iterationscount;
+            public int nmv;
+            public int terminationtype;
+            public double r2;
+        };
+
+
+
+
+        public const double defaultprecision = 1.0E-6;
+
+
+        /*************************************************************************
+        This function initializes linear CG Solver. This solver is used  to  solve
+        symmetric positive definite problems. If you want  to  solve  nonsymmetric
+        (or non-positive definite) problem you may use LinLSQR solver provided  by
+        ALGLIB.
+
+        USAGE:
+        1. User initializes algorithm state with LinCGCreate() call
+        2. User tunes solver parameters with  LinCGSetCond() and other functions
+        3. Optionally, user sets starting point with LinCGSetStartingPoint()
+        4. User  calls LinCGSolveSparse() function which takes algorithm state and
+           SparseMatrix object.
+        5. User calls LinCGResults() to get solution
+        6. Optionally, user may call LinCGSolveSparse()  again  to  solve  another
+           problem  with different matrix and/or right part without reinitializing
+           LinCGState structure.
+          
+        INPUT PARAMETERS:
+            N       -   problem dimension, N>0
+
+        OUTPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+
+          -- ALGLIB --
+             Copyright 14.11.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void lincgcreate(int n,
+            lincgstate state)
+        {
+            int i = 0;
+
+            alglib.ap.assert(n>0, "LinCGCreate: N<=0");
+            state.n = n;
+            state.itsbeforerestart = n;
+            state.itsbeforerupdate = 10;
+            state.epsf = defaultprecision;
+            state.maxits = 0;
+            state.xrep = false;
+            state.running = false;
+            
+            //
+            // * allocate arrays
+            // * set RX to NAN (just for the case user calls Results() without 
+            //   calling SolveSparse()
+            // * set starting point to zero
+            // * we do NOT initialize B here because we assume that user should
+            //   initializate it using LinCGSetB() function. In case he forgets
+            //   to do so, exception will be thrown in the LinCGIteration().
+            //
+            state.rx = new double[state.n];
+            state.startx = new double[state.n];
+            state.b = new double[state.n];
+            for(i=0; i<=state.n-1; i++)
+            {
+                state.rx[i] = Double.NaN;
+                state.startx[i] = 0.0;
+                state.b[i] = 0;
+            }
+            state.cx = new double[state.n];
+            state.p = new double[state.n];
+            state.r = new double[state.n];
+            state.cr = new double[state.n];
+            state.z = new double[state.n];
+            state.cz = new double[state.n];
+            state.x = new double[state.n];
+            state.mv = new double[state.n];
+            state.pv = new double[state.n];
+            updateitersdata(state);
+            state.rstate.ia = new int[0+1];
+            state.rstate.ra = new double[2+1];
+            state.rstate.stage = -1;
+        }
+
+
+        /*************************************************************************
+        This function sets starting point.
+        By default, zero starting point is used.
+
+        INPUT PARAMETERS:
+            X       -   starting point, array[N]
+
+        OUTPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+
+          -- ALGLIB --
+             Copyright 14.11.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void lincgsetstartingpoint(lincgstate state,
+            double[] x)
+        {
+            int i_ = 0;
+
+            alglib.ap.assert(!state.running, "LinCGSetStartingPoint: you can not change starting point because LinCGIteration() function is running");
+            alglib.ap.assert(state.n<=alglib.ap.len(x), "LinCGSetStartingPoint: Length(X)<N");
+            alglib.ap.assert(apserv.isfinitevector(x, state.n), "LinCGSetStartingPoint: X contains infinite or NaN values!");
+            for(i_=0; i_<=state.n-1;i_++)
+            {
+                state.startx[i_] = x[i_];
+            }
+        }
+
+
+        /*************************************************************************
+        This function sets right part. By default, right part is zero.
+
+        INPUT PARAMETERS:
+            B       -   right part, array[N].
+
+        OUTPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+
+          -- ALGLIB --
+             Copyright 14.11.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void lincgsetb(lincgstate state,
+            double[] b)
+        {
+            int i_ = 0;
+
+            alglib.ap.assert(!state.running, "LinCGSetB: you can not set B, because function LinCGIteration is running!");
+            alglib.ap.assert(alglib.ap.len(b)>=state.n, "LinCGSetB: Length(B)<N");
+            alglib.ap.assert(apserv.isfinitevector(b, state.n), "LinCGSetB: B contains infinite or NaN values!");
+            for(i_=0; i_<=state.n-1;i_++)
+            {
+                state.b[i_] = b[i_];
+            }
+        }
+
+
+        /*************************************************************************
+        This function sets stopping criteria.
+
+        INPUT PARAMETERS:
+            EpsF    -   algorithm will be stopped if norm of residual is less than 
+                        EpsF*||b||.
+            MaxIts  -   algorithm will be stopped if number of iterations is  more 
+                        than MaxIts.
+
+        OUTPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+
+        NOTES:
+        If  both  EpsF  and  MaxIts  are  zero then small EpsF will be set to small 
+        value.
+
+          -- ALGLIB --
+             Copyright 14.11.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void lincgsetcond(lincgstate state,
+            double epsf,
+            int maxits)
+        {
+            alglib.ap.assert(!state.running, "LinCGSetCond: you can not change stopping criteria when LinCGIteration() is running");
+            alglib.ap.assert(math.isfinite(epsf) && (double)(epsf)>=(double)(0), "LinCGSetCond: EpsF is negative or contains infinite or NaN values");
+            alglib.ap.assert(maxits>=0, "LinCGSetCond: MaxIts is negative");
+            if( (double)(epsf)==(double)(0) && maxits==0 )
+            {
+                state.epsf = defaultprecision;
+                state.maxits = maxits;
+            }
+            else
+            {
+                state.epsf = epsf;
+                state.maxits = maxits;
+            }
+        }
+
+
+        /*************************************************************************
+        Reverse communication version of linear CG.
+
+          -- ALGLIB --
+             Copyright 14.11.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static bool lincgiteration(lincgstate state)
+        {
+            bool result = new bool();
+            int i = 0;
+            double uvar = 0;
+            double bnorm = 0;
+            double v = 0;
+            int i_ = 0;
+
+            
+            //
+            // Reverse communication preparations
+            // I know it looks ugly, but it works the same way
+            // anywhere from C++ to Python.
+            //
+            // This code initializes locals by:
+            // * random values determined during code
+            //   generation - on first subroutine call
+            // * values from previous call - on subsequent calls
+            //
+            if( state.rstate.stage>=0 )
+            {
+                i = state.rstate.ia[0];
+                uvar = state.rstate.ra[0];
+                bnorm = state.rstate.ra[1];
+                v = state.rstate.ra[2];
+            }
+            else
+            {
+                i = -983;
+                uvar = -989;
+                bnorm = -834;
+                v = 900;
+            }
+            if( state.rstate.stage==0 )
+            {
+                goto lbl_0;
+            }
+            if( state.rstate.stage==1 )
+            {
+                goto lbl_1;
+            }
+            if( state.rstate.stage==2 )
+            {
+                goto lbl_2;
+            }
+            if( state.rstate.stage==3 )
+            {
+                goto lbl_3;
+            }
+            if( state.rstate.stage==4 )
+            {
+                goto lbl_4;
+            }
+            if( state.rstate.stage==5 )
+            {
+                goto lbl_5;
+            }
+            if( state.rstate.stage==6 )
+            {
+                goto lbl_6;
+            }
+            if( state.rstate.stage==7 )
+            {
+                goto lbl_7;
+            }
+            
+            //
+            // Routine body
+            //
+            alglib.ap.assert(alglib.ap.len(state.b)>0, "LinCGIteration: B is not initialized (you must initialize B by LinCGSetB() call");
+            state.running = true;
+            state.repnmv = 0;
+            clearrfields(state);
+            updateitersdata(state);
+            
+            //
+            // Start 0-th iteration
+            //
+            for(i_=0; i_<=state.n-1;i_++)
+            {
+                state.rx[i_] = state.startx[i_];
+            }
+            for(i_=0; i_<=state.n-1;i_++)
+            {
+                state.x[i_] = state.rx[i_];
+            }
+            state.repnmv = state.repnmv+1;
+            clearrfields(state);
+            state.needvmv = true;
+            state.rstate.stage = 0;
+            goto lbl_rcomm;
+        lbl_0:
+            state.needvmv = false;
+            bnorm = 0;
+            state.r2 = 0;
+            state.meritfunction = 0;
+            for(i=0; i<=state.n-1; i++)
+            {
+                state.r[i] = state.b[i]-state.mv[i];
+                state.r2 = state.r2+state.r[i]*state.r[i];
+                state.meritfunction = state.meritfunction+state.mv[i]*state.rx[i]-2*state.b[i]*state.rx[i];
+                bnorm = bnorm+state.b[i]*state.b[i];
+            }
+            bnorm = Math.Sqrt(bnorm);
+            
+            //
+            // Output first report
+            //
+            if( !state.xrep )
+            {
+                goto lbl_8;
+            }
+            for(i_=0; i_<=state.n-1;i_++)
+            {
+                state.x[i_] = state.rx[i_];
+            }
+            clearrfields(state);
+            state.xupdated = true;
+            state.rstate.stage = 1;
+            goto lbl_rcomm;
+        lbl_1:
+            state.xupdated = false;
+        lbl_8:
+            
+            //
+            // Is x0 a solution?
+            //
+            if( !math.isfinite(state.r2) || (double)(Math.Sqrt(state.r2))<=(double)(state.epsf*bnorm) )
+            {
+                state.running = false;
+                if( math.isfinite(state.r2) )
+                {
+                    state.repterminationtype = 1;
+                }
+                else
+                {
+                    state.repterminationtype = -4;
+                }
+                result = false;
+                return result;
+            }
+            
+            //
+            // Calculate Z and P
+            //
+            for(i_=0; i_<=state.n-1;i_++)
+            {
+                state.x[i_] = state.r[i_];
+            }
+            state.repnmv = state.repnmv+1;
+            clearrfields(state);
+            state.needprec = true;
+            state.rstate.stage = 2;
+            goto lbl_rcomm;
+        lbl_2:
+            state.needprec = false;
+            for(i=0; i<=state.n-1; i++)
+            {
+                state.z[i] = state.pv[i];
+                state.p[i] = state.z[i];
+            }
+            
+            //
+            // Other iterations(1..N)
+            //
+            state.repiterationscount = 0;
+        lbl_10:
+            if( false )
+            {
+                goto lbl_11;
+            }
+            state.repiterationscount = state.repiterationscount+1;
+            
+            //
+            // Calculate Alpha
+            //
+            for(i_=0; i_<=state.n-1;i_++)
+            {
+                state.x[i_] = state.p[i_];
+            }
+            state.repnmv = state.repnmv+1;
+            clearrfields(state);
+            state.needvmv = true;
+            state.rstate.stage = 3;
+            goto lbl_rcomm;
+        lbl_3:
+            state.needvmv = false;
+            if( !math.isfinite(state.vmv) || (double)(state.vmv)<=(double)(0) )
+            {
+                
+                //
+                // a) Overflow when calculating VMV
+                // b) non-positive VMV (non-SPD matrix)
+                //
+                state.running = false;
+                if( math.isfinite(state.vmv) )
+                {
+                    state.repterminationtype = -5;
+                }
+                else
+                {
+                    state.repterminationtype = -4;
+                }
+                result = false;
+                return result;
+            }
+            state.alpha = 0;
+            for(i=0; i<=state.n-1; i++)
+            {
+                state.alpha = state.alpha+state.r[i]*state.z[i];
+            }
+            state.alpha = state.alpha/state.vmv;
+            if( !math.isfinite(state.alpha) )
+            {
+                
+                //
+                // Overflow when calculating Alpha
+                //
+                state.running = false;
+                state.repterminationtype = -4;
+                result = false;
+                return result;
+            }
+            
+            //
+            // Next step toward solution
+            //
+            for(i=0; i<=state.n-1; i++)
+            {
+                state.cx[i] = state.rx[i]+state.alpha*state.p[i];
+            }
+            
+            //
+            // Calculate R:
+            // * use recurrent relation to update R
+            // * at every ItsBeforeRUpdate-th iteration recalculate it from scratch, using matrix-vector product
+            //   in case R grows instead of decreasing, algorithm is terminated with positive completion code
+            //
+            if( !(state.itsbeforerupdate==0 || state.repiterationscount%state.itsbeforerupdate!=0) )
+            {
+                goto lbl_12;
+            }
+            
+            //
+            // Calculate R using recurrent formula
+            //
+            for(i=0; i<=state.n-1; i++)
+            {
+                state.cr[i] = state.r[i]-state.alpha*state.mv[i];
+                state.x[i] = state.cr[i];
+            }
+            goto lbl_13;
+        lbl_12:
+            
+            //
+            // Calculate R using matrix-vector multiplication
+            //
+            for(i_=0; i_<=state.n-1;i_++)
+            {
+                state.x[i_] = state.cx[i_];
+            }
+            state.repnmv = state.repnmv+1;
+            clearrfields(state);
+            state.needmv = true;
+            state.rstate.stage = 4;
+            goto lbl_rcomm;
+        lbl_4:
+            state.needmv = false;
+            for(i=0; i<=state.n-1; i++)
+            {
+                state.cr[i] = state.b[i]-state.mv[i];
+                state.x[i] = state.cr[i];
+            }
+            
+            //
+            // Calculating merit function
+            // Check emergency stopping criterion
+            //
+            v = 0;
+            for(i=0; i<=state.n-1; i++)
+            {
+                v = v+state.mv[i]*state.cx[i]-2*state.b[i]*state.cx[i];
+            }
+            if( (double)(v)<(double)(state.meritfunction) )
+            {
+                goto lbl_14;
+            }
+            for(i=0; i<=state.n-1; i++)
+            {
+                if( !math.isfinite(state.rx[i]) )
+                {
+                    state.running = false;
+                    state.repterminationtype = -4;
+                    result = false;
+                    return result;
+                }
+            }
+            
+            //
+            //output last report
+            //
+            if( !state.xrep )
+            {
+                goto lbl_16;
+            }
+            for(i_=0; i_<=state.n-1;i_++)
+            {
+                state.x[i_] = state.rx[i_];
+            }
+            clearrfields(state);
+            state.xupdated = true;
+            state.rstate.stage = 5;
+            goto lbl_rcomm;
+        lbl_5:
+            state.xupdated = false;
+        lbl_16:
+            state.running = false;
+            state.repterminationtype = 7;
+            result = false;
+            return result;
+        lbl_14:
+            state.meritfunction = v;
+        lbl_13:
+            for(i_=0; i_<=state.n-1;i_++)
+            {
+                state.rx[i_] = state.cx[i_];
+            }
+            
+            //
+            // calculating RNorm
+            //
+            // NOTE: monotonic decrease of R2 is not guaranteed by algorithm.
+            //
+            state.r2 = 0;
+            for(i=0; i<=state.n-1; i++)
+            {
+                state.r2 = state.r2+state.cr[i]*state.cr[i];
+            }
+            
+            //
+            //output report
+            //
+            if( !state.xrep )
+            {
+                goto lbl_18;
+            }
+            for(i_=0; i_<=state.n-1;i_++)
+            {
+                state.x[i_] = state.rx[i_];
+            }
+            clearrfields(state);
+            state.xupdated = true;
+            state.rstate.stage = 6;
+            goto lbl_rcomm;
+        lbl_6:
+            state.xupdated = false;
+        lbl_18:
+            
+            //
+            //stopping criterion
+            //achieved the required precision
+            //
+            if( !math.isfinite(state.r2) || (double)(Math.Sqrt(state.r2))<=(double)(state.epsf*bnorm) )
+            {
+                state.running = false;
+                if( math.isfinite(state.r2) )
+                {
+                    state.repterminationtype = 1;
+                }
+                else
+                {
+                    state.repterminationtype = -4;
+                }
+                result = false;
+                return result;
+            }
+            if( state.repiterationscount>=state.maxits && state.maxits>0 )
+            {
+                for(i=0; i<=state.n-1; i++)
+                {
+                    if( !math.isfinite(state.rx[i]) )
+                    {
+                        state.running = false;
+                        state.repterminationtype = -4;
+                        result = false;
+                        return result;
+                    }
+                }
+                
+                //
+                //if X is finite number
+                //
+                state.running = false;
+                state.repterminationtype = 5;
+                result = false;
+                return result;
+            }
+            for(i_=0; i_<=state.n-1;i_++)
+            {
+                state.x[i_] = state.cr[i_];
+            }
+            
+            //
+            //prepere of parameters for next iteration
+            //
+            state.repnmv = state.repnmv+1;
+            clearrfields(state);
+            state.needprec = true;
+            state.rstate.stage = 7;
+            goto lbl_rcomm;
+        lbl_7:
+            state.needprec = false;
+            for(i_=0; i_<=state.n-1;i_++)
+            {
+                state.cz[i_] = state.pv[i_];
+            }
+            if( state.repiterationscount%state.itsbeforerestart!=0 )
+            {
+                state.beta = 0;
+                uvar = 0;
+                for(i=0; i<=state.n-1; i++)
+                {
+                    state.beta = state.beta+state.cz[i]*state.cr[i];
+                    uvar = uvar+state.z[i]*state.r[i];
+                }
+                
+                //
+                //check that UVar is't INF or is't zero
+                //
+                if( !math.isfinite(uvar) || (double)(uvar)==(double)(0) )
+                {
+                    state.running = false;
+                    state.repterminationtype = -4;
+                    result = false;
+                    return result;
+                }
+                
+                //
+                //calculate .BETA
+                //
+                state.beta = state.beta/uvar;
+                
+                //
+                //check that .BETA neither INF nor NaN
+                //
+                if( !math.isfinite(state.beta) )
+                {
+                    state.running = false;
+                    state.repterminationtype = -1;
+                    result = false;
+                    return result;
+                }
+                for(i=0; i<=state.n-1; i++)
+                {
+                    state.p[i] = state.cz[i]+state.beta*state.p[i];
+                }
+            }
+            else
+            {
+                for(i_=0; i_<=state.n-1;i_++)
+                {
+                    state.p[i_] = state.cz[i_];
+                }
+            }
+            
+            //
+            //prepere data for next iteration
+            //
+            for(i=0; i<=state.n-1; i++)
+            {
+                
+                //
+                //write (k+1)th iteration to (k )th iteration
+                //
+                state.r[i] = state.cr[i];
+                state.z[i] = state.cz[i];
+            }
+            goto lbl_10;
+        lbl_11:
+            result = false;
+            return result;
+            
+            //
+            // Saving state
+            //
+        lbl_rcomm:
+            result = true;
+            state.rstate.ia[0] = i;
+            state.rstate.ra[0] = uvar;
+            state.rstate.ra[1] = bnorm;
+            state.rstate.ra[2] = v;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Procedure for solution of A*x=b with sparse A.
+
+        INPUT PARAMETERS:
+            State   -   algorithm state
+            A       -   sparse matrix in the CRS format (you MUST contvert  it  to 
+                        CRS format by calling SparseConvertToCRS() function).
+            IsUpper -   whether upper or lower triangle of A is used:
+                        * IsUpper=True  => only upper triangle is used and lower
+                                           triangle is not referenced at all 
+                        * IsUpper=False => only lower triangle is used and upper
+                                           triangle is not referenced at all
+            B       -   right part, array[N]
+
+        RESULT:
+            This function returns no result.
+            You can get solution by calling LinCGResults()
+
+          -- ALGLIB --
+             Copyright 14.11.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void lincgsolvesparse(lincgstate state,
+            sparse.sparsematrix a,
+            bool isupper,
+            double[] b)
+        {
+            double vmv = 0;
+            int i_ = 0;
+
+            alglib.ap.assert(alglib.ap.len(b)>=state.n, "LinCGSetB: Length(B)<N");
+            alglib.ap.assert(apserv.isfinitevector(b, state.n), "LinCGSetB: B contains infinite or NaN values!");
+            lincgrestart(state);
+            lincgsetb(state, b);
+            while( lincgiteration(state) )
+            {
+                if( state.needmv )
+                {
+                    sparse.sparsesmv(a, isupper, state.x, ref state.mv);
+                }
+                if( state.needvmv )
+                {
+                    sparse.sparsesmv(a, isupper, state.x, ref state.mv);
+                    vmv = 0.0;
+                    for(i_=0; i_<=state.n-1;i_++)
+                    {
+                        vmv += state.x[i_]*state.mv[i_];
+                    }
+                    state.vmv = vmv;
+                }
+                if( state.needprec )
+                {
+                    for(i_=0; i_<=state.n-1;i_++)
+                    {
+                        state.pv[i_] = state.x[i_];
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        CG-solver: results.
+
+        This function must be called after LinCGSolve
+
+        INPUT PARAMETERS:
+            State   -   algorithm state
+
+        OUTPUT PARAMETERS:
+            X       -   array[N], solution
+            Rep     -   optimization report:
+                        * Rep.TerminationType completetion code:
+                            * -5    input matrix is either not positive definite,
+                                    too large or too small                            
+                            * -4    overflow/underflow during solution
+                                    (ill conditioned problem)
+                            *  1    ||residual||<=EpsF*||b||
+                            *  5    MaxIts steps was taken
+                            *  7    rounding errors prevent further progress,
+                                    best point found is returned
+                        * Rep.IterationsCount contains iterations count
+                        * NMV countains number of matrix-vector calculations
+
+          -- ALGLIB --
+             Copyright 14.11.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void lincgresults(lincgstate state,
+            ref double[] x,
+            lincgreport rep)
+        {
+            int i_ = 0;
+
+            x = new double[0];
+
+            alglib.ap.assert(!state.running, "LinCGResult: you can not get result, because function LinCGIteration has been launched!");
+            if( alglib.ap.len(x)<state.n )
+            {
+                x = new double[state.n];
+            }
+            for(i_=0; i_<=state.n-1;i_++)
+            {
+                x[i_] = state.rx[i_];
+            }
+            rep.iterationscount = state.repiterationscount;
+            rep.nmv = state.repnmv;
+            rep.terminationtype = state.repterminationtype;
+            rep.r2 = state.r2;
+        }
+
+
+        /*************************************************************************
+        This function sets restart frequency. By default, algorithm  is  restarted
+        after N subsequent iterations.
+
+          -- ALGLIB --
+             Copyright 14.11.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void lincgsetrestartfreq(lincgstate state,
+            int srf)
+        {
+            alglib.ap.assert(!state.running, "LinCGSetRestartFreq: you can not change restart frequency when LinCGIteration() is running");
+            alglib.ap.assert(srf>0, "LinCGSetRestartFreq: non-positive SRF");
+            state.itsbeforerestart = srf;
+        }
+
+
+        /*************************************************************************
+        This function sets frequency of residual recalculations.
+
+        Algorithm updates residual r_k using iterative formula,  but  recalculates
+        it from scratch after each 10 iterations. It is done to avoid accumulation
+        of numerical errors and to stop algorithm when r_k starts to grow.
+
+        Such low update frequence (1/10) gives very  little  overhead,  but  makes
+        algorithm a bit more robust against numerical errors. However, you may
+        change it 
+
+        INPUT PARAMETERS:
+            Freq    -   desired update frequency, Freq>=0.
+                        Zero value means that no updates will be done.
+
+          -- ALGLIB --
+             Copyright 14.11.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void lincgsetrupdatefreq(lincgstate state,
+            int freq)
+        {
+            alglib.ap.assert(!state.running, "LinCGSetRUpdateFreq: you can not change update frequency when LinCGIteration() is running");
+            alglib.ap.assert(freq>=0, "LinCGSetRUpdateFreq: non-positive Freq");
+            state.itsbeforerupdate = freq;
+        }
+
+
+        /*************************************************************************
+        This function turns on/off reporting.
+
+        INPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+            NeedXRep-   whether iteration reports are needed or not
+
+        If NeedXRep is True, algorithm will call rep() callback function if  it is
+        provided to MinCGOptimize().
+
+          -- ALGLIB --
+             Copyright 14.11.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void lincgsetxrep(lincgstate state,
+            bool needxrep)
+        {
+            state.xrep = needxrep;
+        }
+
+
+        /*************************************************************************
+        Procedure for restart function LinCGIteration
+
+          -- ALGLIB --
+             Copyright 14.11.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void lincgrestart(lincgstate state)
+        {
+            state.rstate.ia = new int[0+1];
+            state.rstate.ra = new double[2+1];
+            state.rstate.stage = -1;
+            clearrfields(state);
+        }
+
+
+        /*************************************************************************
+        Clears request fileds (to be sure that we don't forgot to clear something)
+        *************************************************************************/
+        private static void clearrfields(lincgstate state)
+        {
+            state.xupdated = false;
+            state.needmv = false;
+            state.needmtv = false;
+            state.needmv2 = false;
+            state.needvmv = false;
+            state.needprec = false;
+        }
+
+
+        /*************************************************************************
+        Clears request fileds (to be sure that we don't forgot to clear something)
+        *************************************************************************/
+        private static void updateitersdata(lincgstate state)
+        {
+            state.repiterationscount = 0;
+            state.repnmv = 0;
+            state.repterminationtype = 0;
+        }
+
+
+    }
+    public class nleq
+    {
+        public class nleqstate
+        {
+            public int n;
+            public int m;
+            public double epsf;
+            public int maxits;
+            public bool xrep;
+            public double stpmax;
+            public double[] x;
+            public double f;
+            public double[] fi;
+            public double[,] j;
+            public bool needf;
+            public bool needfij;
+            public bool xupdated;
+            public rcommstate rstate;
+            public int repiterationscount;
+            public int repnfunc;
+            public int repnjac;
+            public int repterminationtype;
+            public double[] xbase;
+            public double fbase;
+            public double fprev;
+            public double[] candstep;
+            public double[] rightpart;
+            public double[] cgbuf;
+            public nleqstate()
+            {
+                x = new double[0];
+                fi = new double[0];
+                j = new double[0,0];
+                rstate = new rcommstate();
+                xbase = new double[0];
+                candstep = new double[0];
+                rightpart = new double[0];
+                cgbuf = new double[0];
+            }
+        };
+
+
+        public class nleqreport
+        {
+            public int iterationscount;
+            public int nfunc;
+            public int njac;
+            public int terminationtype;
+        };
+
+
+
+
+        public const int armijomaxfev = 20;
+
+
+        /*************************************************************************
+                        LEVENBERG-MARQUARDT-LIKE NONLINEAR SOLVER
+
+        DESCRIPTION:
+        This algorithm solves system of nonlinear equations
+            F[0](x[0], ..., x[n-1])   = 0
+            F[1](x[0], ..., x[n-1])   = 0
+            ...
+            F[M-1](x[0], ..., x[n-1]) = 0
+        with M/N do not necessarily coincide.  Algorithm  converges  quadratically
+        under following conditions:
+            * the solution set XS is nonempty
+            * for some xs in XS there exist such neighbourhood N(xs) that:
+              * vector function F(x) and its Jacobian J(x) are continuously
+                differentiable on N
+              * ||F(x)|| provides local error bound on N, i.e. there  exists  such
+                c1, that ||F(x)||>c1*distance(x,XS)
+        Note that these conditions are much more weaker than usual non-singularity
+        conditions. For example, algorithm will converge for any  affine  function
+        F (whether its Jacobian singular or not).
+
+
+        REQUIREMENTS:
+        Algorithm will request following information during its operation:
+        * function vector F[] and Jacobian matrix at given point X
+        * value of merit function f(x)=F[0]^2(x)+...+F[M-1]^2(x) at given point X
+
+
+        USAGE:
+        1. User initializes algorithm state with NLEQCreateLM() call
+        2. User tunes solver parameters with  NLEQSetCond(),  NLEQSetStpMax()  and
+           other functions
+        3. User  calls  NLEQSolve()  function  which  takes  algorithm  state  and
+           pointers (delegates, etc.) to callback functions which calculate  merit
+           function value and Jacobian.
+        4. User calls NLEQResults() to get solution
+        5. Optionally, user may call NLEQRestartFrom() to  solve  another  problem
+           with same parameters (N/M) but another starting  point  and/or  another
+           function vector. NLEQRestartFrom() allows to reuse already  initialized
+           structure.
+
+
+        INPUT PARAMETERS:
+            N       -   space dimension, N>1:
+                        * if provided, only leading N elements of X are used
+                        * if not provided, determined automatically from size of X
+            M       -   system size
+            X       -   starting point
+
+
+        OUTPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+
+
+        NOTES:
+        1. you may tune stopping conditions with NLEQSetCond() function
+        2. if target function contains exp() or other fast growing functions,  and
+           optimization algorithm makes too large steps which leads  to  overflow,
+           use NLEQSetStpMax() function to bound algorithm's steps.
+        3. this  algorithm  is  a  slightly  modified implementation of the method
+           described  in  'Levenberg-Marquardt  method  for constrained  nonlinear
+           equations with strong local convergence properties' by Christian Kanzow
+           Nobuo Yamashita and Masao Fukushima and further  developed  in  'On the
+           convergence of a New Levenberg-Marquardt Method'  by  Jin-yan  Fan  and
+           Ya-Xiang Yuan.
+
+
+          -- ALGLIB --
+             Copyright 20.08.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void nleqcreatelm(int n,
+            int m,
+            double[] x,
+            nleqstate state)
+        {
+            alglib.ap.assert(n>=1, "NLEQCreateLM: N<1!");
+            alglib.ap.assert(m>=1, "NLEQCreateLM: M<1!");
+            alglib.ap.assert(alglib.ap.len(x)>=n, "NLEQCreateLM: Length(X)<N!");
+            alglib.ap.assert(apserv.isfinitevector(x, n), "NLEQCreateLM: X contains infinite or NaN values!");
+            
+            //
+            // Initialize
+            //
+            state.n = n;
+            state.m = m;
+            nleqsetcond(state, 0, 0);
+            nleqsetxrep(state, false);
+            nleqsetstpmax(state, 0);
+            state.x = new double[n];
+            state.xbase = new double[n];
+            state.j = new double[m, n];
+            state.fi = new double[m];
+            state.rightpart = new double[n];
+            state.candstep = new double[n];
+            nleqrestartfrom(state, x);
+        }
+
+
+        /*************************************************************************
+        This function sets stopping conditions for the nonlinear solver
+
+        INPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+            EpsF    -   >=0
+                        The subroutine finishes  its work if on k+1-th iteration
+                        the condition ||F||<=EpsF is satisfied
+            MaxIts  -   maximum number of iterations. If MaxIts=0, the  number  of
+                        iterations is unlimited.
+
+        Passing EpsF=0 and MaxIts=0 simultaneously will lead to  automatic
+        stopping criterion selection (small EpsF).
+
+        NOTES:
+
+          -- ALGLIB --
+             Copyright 20.08.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void nleqsetcond(nleqstate state,
+            double epsf,
+            int maxits)
+        {
+            alglib.ap.assert(math.isfinite(epsf), "NLEQSetCond: EpsF is not finite number!");
+            alglib.ap.assert((double)(epsf)>=(double)(0), "NLEQSetCond: negative EpsF!");
+            alglib.ap.assert(maxits>=0, "NLEQSetCond: negative MaxIts!");
+            if( (double)(epsf)==(double)(0) && maxits==0 )
+            {
+                epsf = 1.0E-6;
+            }
+            state.epsf = epsf;
+            state.maxits = maxits;
+        }
+
+
+        /*************************************************************************
+        This function turns on/off reporting.
+
+        INPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+            NeedXRep-   whether iteration reports are needed or not
+
+        If NeedXRep is True, algorithm will call rep() callback function if  it is
+        provided to NLEQSolve().
+
+          -- ALGLIB --
+             Copyright 20.08.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void nleqsetxrep(nleqstate state,
+            bool needxrep)
+        {
+            state.xrep = needxrep;
+        }
+
+
+        /*************************************************************************
+        This function sets maximum step length
+
+        INPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+            StpMax  -   maximum step length, >=0. Set StpMax to 0.0,  if you don't
+                        want to limit step length.
+
+        Use this subroutine when target function  contains  exp()  or  other  fast
+        growing functions, and algorithm makes  too  large  steps  which  lead  to
+        overflow. This function allows us to reject steps that are too large  (and
+        therefore expose us to the possible overflow) without actually calculating
+        function value at the x+stp*d.
+
+          -- ALGLIB --
+             Copyright 20.08.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void nleqsetstpmax(nleqstate state,
+            double stpmax)
+        {
+            alglib.ap.assert(math.isfinite(stpmax), "NLEQSetStpMax: StpMax is not finite!");
+            alglib.ap.assert((double)(stpmax)>=(double)(0), "NLEQSetStpMax: StpMax<0!");
+            state.stpmax = stpmax;
+        }
+
+
+        /*************************************************************************
+
+          -- ALGLIB --
+             Copyright 20.03.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static bool nleqiteration(nleqstate state)
+        {
+            bool result = new bool();
+            int n = 0;
+            int m = 0;
+            int i = 0;
+            double lambdaup = 0;
+            double lambdadown = 0;
+            double lambdav = 0;
+            double rho = 0;
+            double mu = 0;
+            double stepnorm = 0;
+            bool b = new bool();
+            int i_ = 0;
+
+            
+            //
+            // Reverse communication preparations
+            // I know it looks ugly, but it works the same way
+            // anywhere from C++ to Python.
+            //
+            // This code initializes locals by:
+            // * random values determined during code
+            //   generation - on first subroutine call
+            // * values from previous call - on subsequent calls
+            //
+            if( state.rstate.stage>=0 )
+            {
+                n = state.rstate.ia[0];
+                m = state.rstate.ia[1];
+                i = state.rstate.ia[2];
+                b = state.rstate.ba[0];
+                lambdaup = state.rstate.ra[0];
+                lambdadown = state.rstate.ra[1];
+                lambdav = state.rstate.ra[2];
+                rho = state.rstate.ra[3];
+                mu = state.rstate.ra[4];
+                stepnorm = state.rstate.ra[5];
+            }
+            else
+            {
+                n = -983;
+                m = -989;
+                i = -834;
+                b = false;
+                lambdaup = -287;
+                lambdadown = 364;
+                lambdav = 214;
+                rho = -338;
+                mu = -686;
+                stepnorm = 912;
+            }
+            if( state.rstate.stage==0 )
+            {
+                goto lbl_0;
+            }
+            if( state.rstate.stage==1 )
+            {
+                goto lbl_1;
+            }
+            if( state.rstate.stage==2 )
+            {
+                goto lbl_2;
+            }
+            if( state.rstate.stage==3 )
+            {
+                goto lbl_3;
+            }
+            if( state.rstate.stage==4 )
+            {
+                goto lbl_4;
+            }
+            
+            //
+            // Routine body
+            //
+            
+            //
+            // Prepare
+            //
+            n = state.n;
+            m = state.m;
+            state.repterminationtype = 0;
+            state.repiterationscount = 0;
+            state.repnfunc = 0;
+            state.repnjac = 0;
+            
+            //
+            // Calculate F/G, initialize algorithm
+            //
+            clearrequestfields(state);
+            state.needf = true;
+            state.rstate.stage = 0;
+            goto lbl_rcomm;
+        lbl_0:
+            state.needf = false;
+            state.repnfunc = state.repnfunc+1;
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.xbase[i_] = state.x[i_];
+            }
+            state.fbase = state.f;
+            state.fprev = math.maxrealnumber;
+            if( !state.xrep )
+            {
+                goto lbl_5;
+            }
+            
+            //
+            // progress report
+            //
+            clearrequestfields(state);
+            state.xupdated = true;
+            state.rstate.stage = 1;
+            goto lbl_rcomm;
+        lbl_1:
+            state.xupdated = false;
+        lbl_5:
+            if( (double)(state.f)<=(double)(math.sqr(state.epsf)) )
+            {
+                state.repterminationtype = 1;
+                result = false;
+                return result;
+            }
+            
+            //
+            // Main cycle
+            //
+            lambdaup = 10;
+            lambdadown = 0.3;
+            lambdav = 0.001;
+            rho = 1;
+        lbl_7:
+            if( false )
+            {
+                goto lbl_8;
+            }
+            
+            //
+            // Get Jacobian;
+            // before we get to this point we already have State.XBase filled
+            // with current point and State.FBase filled with function value
+            // at XBase
+            //
+            clearrequestfields(state);
+            state.needfij = true;
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.xbase[i_];
+            }
+            state.rstate.stage = 2;
+            goto lbl_rcomm;
+        lbl_2:
+            state.needfij = false;
+            state.repnfunc = state.repnfunc+1;
+            state.repnjac = state.repnjac+1;
+            ablas.rmatrixmv(n, m, state.j, 0, 0, 1, state.fi, 0, ref state.rightpart, 0);
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.rightpart[i_] = -1*state.rightpart[i_];
+            }
+            
+            //
+            // Inner cycle: find good lambda
+            //
+        lbl_9:
+            if( false )
+            {
+                goto lbl_10;
+            }
+            
+            //
+            // Solve (J^T*J + (Lambda+Mu)*I)*y = J^T*F
+            // to get step d=-y where:
+            // * Mu=||F|| - is damping parameter for nonlinear system
+            // * Lambda   - is additional Levenberg-Marquardt parameter
+            //              for better convergence when far away from minimum
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                state.candstep[i] = 0;
+            }
+            fbls.fblssolvecgx(state.j, m, n, lambdav, state.rightpart, ref state.candstep, ref state.cgbuf);
+            
+            //
+            // Normalize step (it must be no more than StpMax)
+            //
+            stepnorm = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                if( (double)(state.candstep[i])!=(double)(0) )
+                {
+                    stepnorm = 1;
+                    break;
+                }
+            }
+            linmin.linminnormalized(ref state.candstep, ref stepnorm, n);
+            if( (double)(state.stpmax)!=(double)(0) )
+            {
+                stepnorm = Math.Min(stepnorm, state.stpmax);
+            }
+            
+            //
+            // Test new step - is it good enough?
+            // * if not, Lambda is increased and we try again.
+            // * if step is good, we decrease Lambda and move on.
+            //
+            // We can break this cycle on two occasions:
+            // * step is so small that x+step==x (in floating point arithmetics)
+            // * lambda is so large
+            //
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.xbase[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.x[i_] + stepnorm*state.candstep[i_];
+            }
+            b = true;
+            for(i=0; i<=n-1; i++)
+            {
+                if( (double)(state.x[i])!=(double)(state.xbase[i]) )
+                {
+                    b = false;
+                    break;
+                }
+            }
+            if( b )
+            {
+                
+                //
+                // Step is too small, force zero step and break
+                //
+                stepnorm = 0;
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    state.x[i_] = state.xbase[i_];
+                }
+                state.f = state.fbase;
+                goto lbl_10;
+            }
+            clearrequestfields(state);
+            state.needf = true;
+            state.rstate.stage = 3;
+            goto lbl_rcomm;
+        lbl_3:
+            state.needf = false;
+            state.repnfunc = state.repnfunc+1;
+            if( (double)(state.f)<(double)(state.fbase) )
+            {
+                
+                //
+                // function value decreased, move on
+                //
+                decreaselambda(ref lambdav, ref rho, lambdadown);
+                goto lbl_10;
+            }
+            if( !increaselambda(ref lambdav, ref rho, lambdaup) )
+            {
+                
+                //
+                // Lambda is too large (near overflow), force zero step and break
+                //
+                stepnorm = 0;
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    state.x[i_] = state.xbase[i_];
+                }
+                state.f = state.fbase;
+                goto lbl_10;
+            }
+            goto lbl_9;
+        lbl_10:
+            
+            //
+            // Accept step:
+            // * new position
+            // * new function value
+            //
+            state.fbase = state.f;
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.xbase[i_] = state.xbase[i_] + stepnorm*state.candstep[i_];
+            }
+            state.repiterationscount = state.repiterationscount+1;
+            
+            //
+            // Report new iteration
+            //
+            if( !state.xrep )
+            {
+                goto lbl_11;
+            }
+            clearrequestfields(state);
+            state.xupdated = true;
+            state.f = state.fbase;
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.xbase[i_];
+            }
+            state.rstate.stage = 4;
+            goto lbl_rcomm;
+        lbl_4:
+            state.xupdated = false;
+        lbl_11:
+            
+            //
+            // Test stopping conditions on F, step (zero/non-zero) and MaxIts;
+            // If one of the conditions is met, RepTerminationType is changed.
+            //
+            if( (double)(Math.Sqrt(state.f))<=(double)(state.epsf) )
+            {
+                state.repterminationtype = 1;
+            }
+            if( (double)(stepnorm)==(double)(0) && state.repterminationtype==0 )
+            {
+                state.repterminationtype = -4;
+            }
+            if( state.repiterationscount>=state.maxits && state.maxits>0 )
+            {
+                state.repterminationtype = 5;
+            }
+            if( state.repterminationtype!=0 )
+            {
+                goto lbl_8;
+            }
+            
+            //
+            // Now, iteration is finally over
+            //
+            goto lbl_7;
+        lbl_8:
+            result = false;
+            return result;
+            
+            //
+            // Saving state
+            //
+        lbl_rcomm:
+            result = true;
+            state.rstate.ia[0] = n;
+            state.rstate.ia[1] = m;
+            state.rstate.ia[2] = i;
+            state.rstate.ba[0] = b;
+            state.rstate.ra[0] = lambdaup;
+            state.rstate.ra[1] = lambdadown;
+            state.rstate.ra[2] = lambdav;
+            state.rstate.ra[3] = rho;
+            state.rstate.ra[4] = mu;
+            state.rstate.ra[5] = stepnorm;
+            return result;
+        }
+
+
+        /*************************************************************************
+        NLEQ solver results
+
+        INPUT PARAMETERS:
+            State   -   algorithm state.
+
+        OUTPUT PARAMETERS:
+            X       -   array[0..N-1], solution
+            Rep     -   optimization report:
+                        * Rep.TerminationType completetion code:
+                            * -4    ERROR:  algorithm   has   converged   to   the
+                                    stationary point Xf which is local minimum  of
+                                    f=F[0]^2+...+F[m-1]^2, but is not solution  of
+                                    nonlinear system.
+                            *  1    sqrt(f)<=EpsF.
+                            *  5    MaxIts steps was taken
+                            *  7    stopping conditions are too stringent,
+                                    further improvement is impossible
+                        * Rep.IterationsCount contains iterations count
+                        * NFEV countains number of function calculations
+                        * ActiveConstraints contains number of active constraints
+
+          -- ALGLIB --
+             Copyright 20.08.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void nleqresults(nleqstate state,
+            ref double[] x,
+            nleqreport rep)
+        {
+            x = new double[0];
+
+            nleqresultsbuf(state, ref x, rep);
+        }
+
+
+        /*************************************************************************
+        NLEQ solver results
+
+        Buffered implementation of NLEQResults(), which uses pre-allocated  buffer
+        to store X[]. If buffer size is  too  small,  it  resizes  buffer.  It  is
+        intended to be used in the inner cycles of performance critical algorithms
+        where array reallocation penalty is too large to be ignored.
+
+          -- ALGLIB --
+             Copyright 20.08.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void nleqresultsbuf(nleqstate state,
+            ref double[] x,
+            nleqreport rep)
+        {
+            int i_ = 0;
+
+            if( alglib.ap.len(x)<state.n )
+            {
+                x = new double[state.n];
+            }
+            for(i_=0; i_<=state.n-1;i_++)
+            {
+                x[i_] = state.xbase[i_];
+            }
+            rep.iterationscount = state.repiterationscount;
+            rep.nfunc = state.repnfunc;
+            rep.njac = state.repnjac;
+            rep.terminationtype = state.repterminationtype;
+        }
+
+
+        /*************************************************************************
+        This  subroutine  restarts  CG  algorithm from new point. All optimization
+        parameters are left unchanged.
+
+        This  function  allows  to  solve multiple  optimization  problems  (which
+        must have same number of dimensions) without object reallocation penalty.
+
+        INPUT PARAMETERS:
+            State   -   structure used for reverse communication previously
+                        allocated with MinCGCreate call.
+            X       -   new starting point.
+            BndL    -   new lower bounds
+            BndU    -   new upper bounds
+
+          -- ALGLIB --
+             Copyright 30.07.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void nleqrestartfrom(nleqstate state,
+            double[] x)
+        {
+            int i_ = 0;
+
+            alglib.ap.assert(alglib.ap.len(x)>=state.n, "NLEQRestartFrom: Length(X)<N!");
+            alglib.ap.assert(apserv.isfinitevector(x, state.n), "NLEQRestartFrom: X contains infinite or NaN values!");
+            for(i_=0; i_<=state.n-1;i_++)
+            {
+                state.x[i_] = x[i_];
+            }
+            state.rstate.ia = new int[2+1];
+            state.rstate.ba = new bool[0+1];
+            state.rstate.ra = new double[5+1];
+            state.rstate.stage = -1;
+            clearrequestfields(state);
+        }
+
+
+        /*************************************************************************
+        Clears request fileds (to be sure that we don't forgot to clear something)
+        *************************************************************************/
+        private static void clearrequestfields(nleqstate state)
+        {
+            state.needf = false;
+            state.needfij = false;
+            state.xupdated = false;
+        }
+
+
+        /*************************************************************************
+        Increases lambda, returns False when there is a danger of overflow
+        *************************************************************************/
+        private static bool increaselambda(ref double lambdav,
+            ref double nu,
+            double lambdaup)
+        {
+            bool result = new bool();
+            double lnlambda = 0;
+            double lnnu = 0;
+            double lnlambdaup = 0;
+            double lnmax = 0;
+
+            result = false;
+            lnlambda = Math.Log(lambdav);
+            lnlambdaup = Math.Log(lambdaup);
+            lnnu = Math.Log(nu);
+            lnmax = 0.5*Math.Log(math.maxrealnumber);
+            if( (double)(lnlambda+lnlambdaup+lnnu)>(double)(lnmax) )
+            {
+                return result;
+            }
+            if( (double)(lnnu+Math.Log(2))>(double)(lnmax) )
+            {
+                return result;
+            }
+            lambdav = lambdav*lambdaup*nu;
+            nu = nu*2;
+            result = true;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Decreases lambda, but leaves it unchanged when there is danger of underflow.
+        *************************************************************************/
+        private static void decreaselambda(ref double lambdav,
+            ref double nu,
+            double lambdadown)
+        {
+            nu = 1;
+            if( (double)(Math.Log(lambdav)+Math.Log(lambdadown))<(double)(Math.Log(math.minrealnumber)) )
+            {
+                lambdav = math.minrealnumber;
+            }
+            else
+            {
+                lambdav = lambdav*lambdadown;
+            }
+        }
+
+
+    }
+}
+
Index: /trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.5.0/ALGLIB-3.5.0/specialfunctions.cs
===================================================================
--- /trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.5.0/ALGLIB-3.5.0/specialfunctions.cs	(revision 7673)
+++ /trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.5.0/ALGLIB-3.5.0/specialfunctions.cs	(revision 7673)
@@ -0,0 +1,8993 @@
+/*************************************************************************
+Copyright (c) Sergey Bochkanov (ALGLIB project).
+
+>>> SOURCE LICENSE >>>
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation (www.fsf.org); either version 2 of the
+License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+A copy of the GNU General Public License is available at
+http://www.fsf.org/licensing/licenses
+>>> END OF LICENSE >>>
+*************************************************************************/
+#pragma warning disable 162
+#pragma warning disable 219
+using System;
+
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    Gamma function
+
+    Input parameters:
+        X   -   argument
+
+    Domain:
+        0 < X < 171.6
+        -170 < X < 0, X is not an integer.
+
+    Relative error:
+     arithmetic   domain     # trials      peak         rms
+        IEEE    -170,-33      20000       2.3e-15     3.3e-16
+        IEEE     -33,  33     20000       9.4e-16     2.2e-16
+        IEEE      33, 171.6   20000       2.3e-15     3.2e-16
+
+    Cephes Math Library Release 2.8:  June, 2000
+    Original copyright 1984, 1987, 1989, 1992, 2000 by Stephen L. Moshier
+    Translated to AlgoPascal by Bochkanov Sergey (2005, 2006, 2007).
+    *************************************************************************/
+    public static double gammafunction(double x)
+    {
+
+        double result = gammafunc.gammafunction(x);
+        return result;
+    }
+
+    /*************************************************************************
+    Natural logarithm of gamma function
+
+    Input parameters:
+        X       -   argument
+
+    Result:
+        logarithm of the absolute value of the Gamma(X).
+
+    Output parameters:
+        SgnGam  -   sign(Gamma(X))
+
+    Domain:
+        0 < X < 2.55e305
+        -2.55e305 < X < 0, X is not an integer.
+
+    ACCURACY:
+    arithmetic      domain        # trials     peak         rms
+       IEEE    0, 3                 28000     5.4e-16     1.1e-16
+       IEEE    2.718, 2.556e305     40000     3.5e-16     8.3e-17
+    The error criterion was relative when the function magnitude
+    was greater than one but absolute when it was less than one.
+
+    The following test used the relative error criterion, though
+    at certain points the relative error could be much higher than
+    indicated.
+       IEEE    -200, -4             10000     4.8e-16     1.3e-16
+
+    Cephes Math Library Release 2.8:  June, 2000
+    Copyright 1984, 1987, 1989, 1992, 2000 by Stephen L. Moshier
+    Translated to AlgoPascal by Bochkanov Sergey (2005, 2006, 2007).
+    *************************************************************************/
+    public static double lngamma(double x, out double sgngam)
+    {
+        sgngam = 0;
+        double result = gammafunc.lngamma(x, ref sgngam);
+        return result;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    Error function
+
+    The integral is
+
+                              x
+                               -
+                    2         | |          2
+      erf(x)  =  --------     |    exp( - t  ) dt.
+                 sqrt(pi)   | |
+                             -
+                              0
+
+    For 0 <= |x| < 1, erf(x) = x * P4(x**2)/Q5(x**2); otherwise
+    erf(x) = 1 - erfc(x).
+
+
+    ACCURACY:
+
+                         Relative error:
+    arithmetic   domain     # trials      peak         rms
+       IEEE      0,1         30000       3.7e-16     1.0e-16
+
+    Cephes Math Library Release 2.8:  June, 2000
+    Copyright 1984, 1987, 1988, 1992, 2000 by Stephen L. Moshier
+    *************************************************************************/
+    public static double errorfunction(double x)
+    {
+
+        double result = normaldistr.errorfunction(x);
+        return result;
+    }
+
+    /*************************************************************************
+    Complementary error function
+
+     1 - erf(x) =
+
+                              inf.
+                                -
+                     2         | |          2
+      erfc(x)  =  --------     |    exp( - t  ) dt
+                  sqrt(pi)   | |
+                              -
+                               x
+
+
+    For small x, erfc(x) = 1 - erf(x); otherwise rational
+    approximations are computed.
+
+
+    ACCURACY:
+
+                         Relative error:
+    arithmetic   domain     # trials      peak         rms
+       IEEE      0,26.6417   30000       5.7e-14     1.5e-14
+
+    Cephes Math Library Release 2.8:  June, 2000
+    Copyright 1984, 1987, 1988, 1992, 2000 by Stephen L. Moshier
+    *************************************************************************/
+    public static double errorfunctionc(double x)
+    {
+
+        double result = normaldistr.errorfunctionc(x);
+        return result;
+    }
+
+    /*************************************************************************
+    Normal distribution function
+
+    Returns the area under the Gaussian probability density
+    function, integrated from minus infinity to x:
+
+                               x
+                                -
+                      1        | |          2
+       ndtr(x)  = ---------    |    exp( - t /2 ) dt
+                  sqrt(2pi)  | |
+                              -
+                             -inf.
+
+                =  ( 1 + erf(z) ) / 2
+                =  erfc(z) / 2
+
+    where z = x/sqrt(2). Computation is via the functions
+    erf and erfc.
+
+
+    ACCURACY:
+
+                         Relative error:
+    arithmetic   domain     # trials      peak         rms
+       IEEE     -13,0        30000       3.4e-14     6.7e-15
+
+    Cephes Math Library Release 2.8:  June, 2000
+    Copyright 1984, 1987, 1988, 1992, 2000 by Stephen L. Moshier
+    *************************************************************************/
+    public static double normaldistribution(double x)
+    {
+
+        double result = normaldistr.normaldistribution(x);
+        return result;
+    }
+
+    /*************************************************************************
+    Inverse of the error function
+
+    Cephes Math Library Release 2.8:  June, 2000
+    Copyright 1984, 1987, 1988, 1992, 2000 by Stephen L. Moshier
+    *************************************************************************/
+    public static double inverf(double e)
+    {
+
+        double result = normaldistr.inverf(e);
+        return result;
+    }
+
+    /*************************************************************************
+    Inverse of Normal distribution function
+
+    Returns the argument, x, for which the area under the
+    Gaussian probability density function (integrated from
+    minus infinity to x) is equal to y.
+
+
+    For small arguments 0 < y < exp(-2), the program computes
+    z = sqrt( -2.0 * log(y) );  then the approximation is
+    x = z - log(z)/z  - (1/z) P(1/z) / Q(1/z).
+    There are two rational functions P/Q, one for 0 < y < exp(-32)
+    and the other for y up to exp(-2).  For larger arguments,
+    w = y - 0.5, and  x/sqrt(2pi) = w + w**3 R(w**2)/S(w**2)).
+
+    ACCURACY:
+
+                         Relative error:
+    arithmetic   domain        # trials      peak         rms
+       IEEE     0.125, 1        20000       7.2e-16     1.3e-16
+       IEEE     3e-308, 0.135   50000       4.6e-16     9.8e-17
+
+    Cephes Math Library Release 2.8:  June, 2000
+    Copyright 1984, 1987, 1988, 1992, 2000 by Stephen L. Moshier
+    *************************************************************************/
+    public static double invnormaldistribution(double y0)
+    {
+
+        double result = normaldistr.invnormaldistribution(y0);
+        return result;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    Incomplete gamma integral
+
+    The function is defined by
+
+                              x
+                               -
+                      1       | |  -t  a-1
+     igam(a,x)  =   -----     |   e   t   dt.
+                     -      | |
+                    | (a)    -
+                              0
+
+
+    In this implementation both arguments must be positive.
+    The integral is evaluated by either a power series or
+    continued fraction expansion, depending on the relative
+    values of a and x.
+
+    ACCURACY:
+
+                         Relative error:
+    arithmetic   domain     # trials      peak         rms
+       IEEE      0,30       200000       3.6e-14     2.9e-15
+       IEEE      0,100      300000       9.9e-14     1.5e-14
+
+    Cephes Math Library Release 2.8:  June, 2000
+    Copyright 1985, 1987, 2000 by Stephen L. Moshier
+    *************************************************************************/
+    public static double incompletegamma(double a, double x)
+    {
+
+        double result = igammaf.incompletegamma(a, x);
+        return result;
+    }
+
+    /*************************************************************************
+    Complemented incomplete gamma integral
+
+    The function is defined by
+
+
+     igamc(a,x)   =   1 - igam(a,x)
+
+                               inf.
+                                 -
+                        1       | |  -t  a-1
+                  =   -----     |   e   t   dt.
+                       -      | |
+                      | (a)    -
+                                x
+
+
+    In this implementation both arguments must be positive.
+    The integral is evaluated by either a power series or
+    continued fraction expansion, depending on the relative
+    values of a and x.
+
+    ACCURACY:
+
+    Tested at random a, x.
+                   a         x                      Relative error:
+    arithmetic   domain   domain     # trials      peak         rms
+       IEEE     0.5,100   0,100      200000       1.9e-14     1.7e-15
+       IEEE     0.01,0.5  0,100      200000       1.4e-13     1.6e-15
+
+    Cephes Math Library Release 2.8:  June, 2000
+    Copyright 1985, 1987, 2000 by Stephen L. Moshier
+    *************************************************************************/
+    public static double incompletegammac(double a, double x)
+    {
+
+        double result = igammaf.incompletegammac(a, x);
+        return result;
+    }
+
+    /*************************************************************************
+    Inverse of complemented imcomplete gamma integral
+
+    Given p, the function finds x such that
+
+     igamc( a, x ) = p.
+
+    Starting with the approximate value
+
+            3
+     x = a t
+
+     where
+
+     t = 1 - d - ndtri(p) sqrt(d)
+
+    and
+
+     d = 1/9a,
+
+    the routine performs up to 10 Newton iterations to find the
+    root of igamc(a,x) - p = 0.
+
+    ACCURACY:
+
+    Tested at random a, p in the intervals indicated.
+
+                   a        p                      Relative error:
+    arithmetic   domain   domain     # trials      peak         rms
+       IEEE     0.5,100   0,0.5       100000       1.0e-14     1.7e-15
+       IEEE     0.01,0.5  0,0.5       100000       9.0e-14     3.4e-15
+       IEEE    0.5,10000  0,0.5        20000       2.3e-13     3.8e-14
+
+    Cephes Math Library Release 2.8:  June, 2000
+    Copyright 1984, 1987, 1995, 2000 by Stephen L. Moshier
+    *************************************************************************/
+    public static double invincompletegammac(double a, double y0)
+    {
+
+        double result = igammaf.invincompletegammac(a, y0);
+        return result;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    Airy function
+
+    Solution of the differential equation
+
+    y"(x) = xy.
+
+    The function returns the two independent solutions Ai, Bi
+    and their first derivatives Ai'(x), Bi'(x).
+
+    Evaluation is by power series summation for small x,
+    by rational minimax approximations for large x.
+
+
+
+    ACCURACY:
+    Error criterion is absolute when function <= 1, relative
+    when function > 1, except * denotes relative error criterion.
+    For large negative x, the absolute error increases as x^1.5.
+    For large positive x, the relative error increases as x^1.5.
+
+    Arithmetic  domain   function  # trials      peak         rms
+    IEEE        -10, 0     Ai        10000       1.6e-15     2.7e-16
+    IEEE          0, 10    Ai        10000       2.3e-14*    1.8e-15*
+    IEEE        -10, 0     Ai'       10000       4.6e-15     7.6e-16
+    IEEE          0, 10    Ai'       10000       1.8e-14*    1.5e-15*
+    IEEE        -10, 10    Bi        30000       4.2e-15     5.3e-16
+    IEEE        -10, 10    Bi'       30000       4.9e-15     7.3e-16
+
+    Cephes Math Library Release 2.8:  June, 2000
+    Copyright 1984, 1987, 1989, 2000 by Stephen L. Moshier
+    *************************************************************************/
+    public static void airy(double x, out double ai, out double aip, out double bi, out double bip)
+    {
+        ai = 0;
+        aip = 0;
+        bi = 0;
+        bip = 0;
+        airyf.airy(x, ref ai, ref aip, ref bi, ref bip);
+        return;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    Bessel function of order zero
+
+    Returns Bessel function of order zero of the argument.
+
+    The domain is divided into the intervals [0, 5] and
+    (5, infinity). In the first interval the following rational
+    approximation is used:
+
+
+           2         2
+    (w - r  ) (w - r  ) P (w) / Q (w)
+          1         2    3       8
+
+               2
+    where w = x  and the two r's are zeros of the function.
+
+    In the second interval, the Hankel asymptotic expansion
+    is employed with two rational functions of degree 6/6
+    and 7/7.
+
+    ACCURACY:
+
+                         Absolute error:
+    arithmetic   domain     # trials      peak         rms
+       IEEE      0, 30       60000       4.2e-16     1.1e-16
+
+    Cephes Math Library Release 2.8:  June, 2000
+    Copyright 1984, 1987, 1989, 2000 by Stephen L. Moshier
+    *************************************************************************/
+    public static double besselj0(double x)
+    {
+
+        double result = bessel.besselj0(x);
+        return result;
+    }
+
+    /*************************************************************************
+    Bessel function of order one
+
+    Returns Bessel function of order one of the argument.
+
+    The domain is divided into the intervals [0, 8] and
+    (8, infinity). In the first interval a 24 term Chebyshev
+    expansion is used. In the second, the asymptotic
+    trigonometric representation is employed using two
+    rational functions of degree 5/5.
+
+    ACCURACY:
+
+                         Absolute error:
+    arithmetic   domain      # trials      peak         rms
+       IEEE      0, 30       30000       2.6e-16     1.1e-16
+
+    Cephes Math Library Release 2.8:  June, 2000
+    Copyright 1984, 1987, 1989, 2000 by Stephen L. Moshier
+    *************************************************************************/
+    public static double besselj1(double x)
+    {
+
+        double result = bessel.besselj1(x);
+        return result;
+    }
+
+    /*************************************************************************
+    Bessel function of integer order
+
+    Returns Bessel function of order n, where n is a
+    (possibly negative) integer.
+
+    The ratio of jn(x) to j0(x) is computed by backward
+    recurrence.  First the ratio jn/jn-1 is found by a
+    continued fraction expansion.  Then the recurrence
+    relating successive orders is applied until j0 or j1 is
+    reached.
+
+    If n = 0 or 1 the routine for j0 or j1 is called
+    directly.
+
+    ACCURACY:
+
+                         Absolute error:
+    arithmetic   range      # trials      peak         rms
+       IEEE      0, 30        5000       4.4e-16     7.9e-17
+
+
+    Not suitable for large n or x. Use jv() (fractional order) instead.
+
+    Cephes Math Library Release 2.8:  June, 2000
+    Copyright 1984, 1987, 2000 by Stephen L. Moshier
+    *************************************************************************/
+    public static double besseljn(int n, double x)
+    {
+
+        double result = bessel.besseljn(n, x);
+        return result;
+    }
+
+    /*************************************************************************
+    Bessel function of the second kind, order zero
+
+    Returns Bessel function of the second kind, of order
+    zero, of the argument.
+
+    The domain is divided into the intervals [0, 5] and
+    (5, infinity). In the first interval a rational approximation
+    R(x) is employed to compute
+      y0(x)  = R(x)  +   2 * log(x) * j0(x) / PI.
+    Thus a call to j0() is required.
+
+    In the second interval, the Hankel asymptotic expansion
+    is employed with two rational functions of degree 6/6
+    and 7/7.
+
+
+
+    ACCURACY:
+
+     Absolute error, when y0(x) < 1; else relative error:
+
+    arithmetic   domain     # trials      peak         rms
+       IEEE      0, 30       30000       1.3e-15     1.6e-16
+
+    Cephes Math Library Release 2.8:  June, 2000
+    Copyright 1984, 1987, 1989, 2000 by Stephen L. Moshier
+    *************************************************************************/
+    public static double bessely0(double x)
+    {
+
+        double result = bessel.bessely0(x);
+        return result;
+    }
+
+    /*************************************************************************
+    Bessel function of second kind of order one
+
+    Returns Bessel function of the second kind of order one
+    of the argument.
+
+    The domain is divided into the intervals [0, 8] and
+    (8, infinity). In the first interval a 25 term Chebyshev
+    expansion is used, and a call to j1() is required.
+    In the second, the asymptotic trigonometric representation
+    is employed using two rational functions of degree 5/5.
+
+    ACCURACY:
+
+                         Absolute error:
+    arithmetic   domain      # trials      peak         rms
+       IEEE      0, 30       30000       1.0e-15     1.3e-16
+
+    Cephes Math Library Release 2.8:  June, 2000
+    Copyright 1984, 1987, 1989, 2000 by Stephen L. Moshier
+    *************************************************************************/
+    public static double bessely1(double x)
+    {
+
+        double result = bessel.bessely1(x);
+        return result;
+    }
+
+    /*************************************************************************
+    Bessel function of second kind of integer order
+
+    Returns Bessel function of order n, where n is a
+    (possibly negative) integer.
+
+    The function is evaluated by forward recurrence on
+    n, starting with values computed by the routines
+    y0() and y1().
+
+    If n = 0 or 1 the routine for y0 or y1 is called
+    directly.
+
+    ACCURACY:
+                         Absolute error, except relative
+                         when y > 1:
+    arithmetic   domain     # trials      peak         rms
+       IEEE      0, 30       30000       3.4e-15     4.3e-16
+
+    Cephes Math Library Release 2.8:  June, 2000
+    Copyright 1984, 1987, 2000 by Stephen L. Moshier
+    *************************************************************************/
+    public static double besselyn(int n, double x)
+    {
+
+        double result = bessel.besselyn(n, x);
+        return result;
+    }
+
+    /*************************************************************************
+    Modified Bessel function of order zero
+
+    Returns modified Bessel function of order zero of the
+    argument.
+
+    The function is defined as i0(x) = j0( ix ).
+
+    The range is partitioned into the two intervals [0,8] and
+    (8, infinity).  Chebyshev polynomial expansions are employed
+    in each interval.
+
+    ACCURACY:
+
+                         Relative error:
+    arithmetic   domain     # trials      peak         rms
+       IEEE      0,30        30000       5.8e-16     1.4e-16
+
+    Cephes Math Library Release 2.8:  June, 2000
+    Copyright 1984, 1987, 2000 by Stephen L. Moshier
+    *************************************************************************/
+    public static double besseli0(double x)
+    {
+
+        double result = bessel.besseli0(x);
+        return result;
+    }
+
+    /*************************************************************************
+    Modified Bessel function of order one
+
+    Returns modified Bessel function of order one of the
+    argument.
+
+    The function is defined as i1(x) = -i j1( ix ).
+
+    The range is partitioned into the two intervals [0,8] and
+    (8, infinity).  Chebyshev polynomial expansions are employed
+    in each interval.
+
+    ACCURACY:
+
+                         Relative error:
+    arithmetic   domain     # trials      peak         rms
+       IEEE      0, 30       30000       1.9e-15     2.1e-16
+
+    Cephes Math Library Release 2.8:  June, 2000
+    Copyright 1985, 1987, 2000 by Stephen L. Moshier
+    *************************************************************************/
+    public static double besseli1(double x)
+    {
+
+        double result = bessel.besseli1(x);
+        return result;
+    }
+
+    /*************************************************************************
+    Modified Bessel function, second kind, order zero
+
+    Returns modified Bessel function of the second kind
+    of order zero of the argument.
+
+    The range is partitioned into the two intervals [0,8] and
+    (8, infinity).  Chebyshev polynomial expansions are employed
+    in each interval.
+
+    ACCURACY:
+
+    Tested at 2000 random points between 0 and 8.  Peak absolute
+    error (relative when K0 > 1) was 1.46e-14; rms, 4.26e-15.
+                         Relative error:
+    arithmetic   domain     # trials      peak         rms
+       IEEE      0, 30       30000       1.2e-15     1.6e-16
+
+    Cephes Math Library Release 2.8:  June, 2000
+    Copyright 1984, 1987, 2000 by Stephen L. Moshier
+    *************************************************************************/
+    public static double besselk0(double x)
+    {
+
+        double result = bessel.besselk0(x);
+        return result;
+    }
+
+    /*************************************************************************
+    Modified Bessel function, second kind, order one
+
+    Computes the modified Bessel function of the second kind
+    of order one of the argument.
+
+    The range is partitioned into the two intervals [0,2] and
+    (2, infinity).  Chebyshev polynomial expansions are employed
+    in each interval.
+
+    ACCURACY:
+
+                         Relative error:
+    arithmetic   domain     # trials      peak         rms
+       IEEE      0, 30       30000       1.2e-15     1.6e-16
+
+    Cephes Math Library Release 2.8:  June, 2000
+    Copyright 1984, 1987, 2000 by Stephen L. Moshier
+    *************************************************************************/
+    public static double besselk1(double x)
+    {
+
+        double result = bessel.besselk1(x);
+        return result;
+    }
+
+    /*************************************************************************
+    Modified Bessel function, second kind, integer order
+
+    Returns modified Bessel function of the second kind
+    of order n of the argument.
+
+    The range is partitioned into the two intervals [0,9.55] and
+    (9.55, infinity).  An ascending power series is used in the
+    low range, and an asymptotic expansion in the high range.
+
+    ACCURACY:
+
+                         Relative error:
+    arithmetic   domain     # trials      peak         rms
+       IEEE      0,30        90000       1.8e-8      3.0e-10
+
+    Error is high only near the crossover point x = 9.55
+    between the two expansions used.
+
+    Cephes Math Library Release 2.8:  June, 2000
+    Copyright 1984, 1987, 1988, 2000 by Stephen L. Moshier
+    *************************************************************************/
+    public static double besselkn(int nn, double x)
+    {
+
+        double result = bessel.besselkn(nn, x);
+        return result;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    Beta function
+
+
+                      -     -
+                     | (a) | (b)
+    beta( a, b )  =  -----------.
+                        -
+                       | (a+b)
+
+    For large arguments the logarithm of the function is
+    evaluated using lgam(), then exponentiated.
+
+    ACCURACY:
+
+                         Relative error:
+    arithmetic   domain     # trials      peak         rms
+       IEEE       0,30       30000       8.1e-14     1.1e-14
+
+    Cephes Math Library Release 2.0:  April, 1987
+    Copyright 1984, 1987 by Stephen L. Moshier
+    *************************************************************************/
+    public static double beta(double a, double b)
+    {
+
+        double result = betaf.beta(a, b);
+        return result;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    Incomplete beta integral
+
+    Returns incomplete beta integral of the arguments, evaluated
+    from zero to x.  The function is defined as
+
+                     x
+        -            -
+       | (a+b)      | |  a-1     b-1
+     -----------    |   t   (1-t)   dt.
+      -     -     | |
+     | (a) | (b)   -
+                    0
+
+    The domain of definition is 0 <= x <= 1.  In this
+    implementation a and b are restricted to positive values.
+    The integral from x to 1 may be obtained by the symmetry
+    relation
+
+       1 - incbet( a, b, x )  =  incbet( b, a, 1-x ).
+
+    The integral is evaluated by a continued fraction expansion
+    or, when b*x is small, by a power series.
+
+    ACCURACY:
+
+    Tested at uniformly distributed random points (a,b,x) with a and b
+    in "domain" and x between 0 and 1.
+                                           Relative error
+    arithmetic   domain     # trials      peak         rms
+       IEEE      0,5         10000       6.9e-15     4.5e-16
+       IEEE      0,85       250000       2.2e-13     1.7e-14
+       IEEE      0,1000      30000       5.3e-12     6.3e-13
+       IEEE      0,10000    250000       9.3e-11     7.1e-12
+       IEEE      0,100000    10000       8.7e-10     4.8e-11
+    Outputs smaller than the IEEE gradual underflow threshold
+    were excluded from these statistics.
+
+    Cephes Math Library, Release 2.8:  June, 2000
+    Copyright 1984, 1995, 2000 by Stephen L. Moshier
+    *************************************************************************/
+    public static double incompletebeta(double a, double b, double x)
+    {
+
+        double result = ibetaf.incompletebeta(a, b, x);
+        return result;
+    }
+
+    /*************************************************************************
+    Inverse of imcomplete beta integral
+
+    Given y, the function finds x such that
+
+     incbet( a, b, x ) = y .
+
+    The routine performs interval halving or Newton iterations to find the
+    root of incbet(a,b,x) - y = 0.
+
+
+    ACCURACY:
+
+                         Relative error:
+                   x     a,b
+    arithmetic   domain  domain  # trials    peak       rms
+       IEEE      0,1    .5,10000   50000    5.8e-12   1.3e-13
+       IEEE      0,1   .25,100    100000    1.8e-13   3.9e-15
+       IEEE      0,1     0,5       50000    1.1e-12   5.5e-15
+    With a and b constrained to half-integer or integer values:
+       IEEE      0,1    .5,10000   50000    5.8e-12   1.1e-13
+       IEEE      0,1    .5,100    100000    1.7e-14   7.9e-16
+    With a = .5, b constrained to half-integer or integer values:
+       IEEE      0,1    .5,10000   10000    8.3e-11   1.0e-11
+
+    Cephes Math Library Release 2.8:  June, 2000
+    Copyright 1984, 1996, 2000 by Stephen L. Moshier
+    *************************************************************************/
+    public static double invincompletebeta(double a, double b, double y)
+    {
+
+        double result = ibetaf.invincompletebeta(a, b, y);
+        return result;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    Binomial distribution
+
+    Returns the sum of the terms 0 through k of the Binomial
+    probability density:
+
+      k
+      --  ( n )   j      n-j
+      >   (   )  p  (1-p)
+      --  ( j )
+     j=0
+
+    The terms are not summed directly; instead the incomplete
+    beta integral is employed, according to the formula
+
+    y = bdtr( k, n, p ) = incbet( n-k, k+1, 1-p ).
+
+    The arguments must be positive, with p ranging from 0 to 1.
+
+    ACCURACY:
+
+    Tested at random points (a,b,p), with p between 0 and 1.
+
+                  a,b                     Relative error:
+    arithmetic  domain     # trials      peak         rms
+     For p between 0.001 and 1:
+       IEEE     0,100       100000      4.3e-15     2.6e-16
+
+    Cephes Math Library Release 2.8:  June, 2000
+    Copyright 1984, 1987, 1995, 2000 by Stephen L. Moshier
+    *************************************************************************/
+    public static double binomialdistribution(int k, int n, double p)
+    {
+
+        double result = binomialdistr.binomialdistribution(k, n, p);
+        return result;
+    }
+
+    /*************************************************************************
+    Complemented binomial distribution
+
+    Returns the sum of the terms k+1 through n of the Binomial
+    probability density:
+
+      n
+      --  ( n )   j      n-j
+      >   (   )  p  (1-p)
+      --  ( j )
+     j=k+1
+
+    The terms are not summed directly; instead the incomplete
+    beta integral is employed, according to the formula
+
+    y = bdtrc( k, n, p ) = incbet( k+1, n-k, p ).
+
+    The arguments must be positive, with p ranging from 0 to 1.
+
+    ACCURACY:
+
+    Tested at random points (a,b,p).
+
+                  a,b                     Relative error:
+    arithmetic  domain     # trials      peak         rms
+     For p between 0.001 and 1:
+       IEEE     0,100       100000      6.7e-15     8.2e-16
+     For p between 0 and .001:
+       IEEE     0,100       100000      1.5e-13     2.7e-15
+
+    Cephes Math Library Release 2.8:  June, 2000
+    Copyright 1984, 1987, 1995, 2000 by Stephen L. Moshier
+    *************************************************************************/
+    public static double binomialcdistribution(int k, int n, double p)
+    {
+
+        double result = binomialdistr.binomialcdistribution(k, n, p);
+        return result;
+    }
+
+    /*************************************************************************
+    Inverse binomial distribution
+
+    Finds the event probability p such that the sum of the
+    terms 0 through k of the Binomial probability density
+    is equal to the given cumulative probability y.
+
+    This is accomplished using the inverse beta integral
+    function and the relation
+
+    1 - p = incbi( n-k, k+1, y ).
+
+    ACCURACY:
+
+    Tested at random points (a,b,p).
+
+                  a,b                     Relative error:
+    arithmetic  domain     # trials      peak         rms
+     For p between 0.001 and 1:
+       IEEE     0,100       100000      2.3e-14     6.4e-16
+       IEEE     0,10000     100000      6.6e-12     1.2e-13
+     For p between 10^-6 and 0.001:
+       IEEE     0,100       100000      2.0e-12     1.3e-14
+       IEEE     0,10000     100000      1.5e-12     3.2e-14
+
+    Cephes Math Library Release 2.8:  June, 2000
+    Copyright 1984, 1987, 1995, 2000 by Stephen L. Moshier
+    *************************************************************************/
+    public static double invbinomialdistribution(int k, int n, double y)
+    {
+
+        double result = binomialdistr.invbinomialdistribution(k, n, y);
+        return result;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    Calculation of the value of the Chebyshev polynomials of the
+    first and second kinds.
+
+    Parameters:
+        r   -   polynomial kind, either 1 or 2.
+        n   -   degree, n>=0
+        x   -   argument, -1 <= x <= 1
+
+    Result:
+        the value of the Chebyshev polynomial at x
+    *************************************************************************/
+    public static double chebyshevcalculate(int r, int n, double x)
+    {
+
+        double result = chebyshev.chebyshevcalculate(r, n, x);
+        return result;
+    }
+
+    /*************************************************************************
+    Summation of Chebyshev polynomials using Clenshaws recurrence formula.
+
+    This routine calculates
+        c[0]*T0(x) + c[1]*T1(x) + ... + c[N]*TN(x)
+    or
+        c[0]*U0(x) + c[1]*U1(x) + ... + c[N]*UN(x)
+    depending on the R.
+
+    Parameters:
+        r   -   polynomial kind, either 1 or 2.
+        n   -   degree, n>=0
+        x   -   argument
+
+    Result:
+        the value of the Chebyshev polynomial at x
+    *************************************************************************/
+    public static double chebyshevsum(double[] c, int r, int n, double x)
+    {
+
+        double result = chebyshev.chebyshevsum(c, r, n, x);
+        return result;
+    }
+
+    /*************************************************************************
+    Representation of Tn as C[0] + C[1]*X + ... + C[N]*X^N
+
+    Input parameters:
+        N   -   polynomial degree, n>=0
+
+    Output parameters:
+        C   -   coefficients
+    *************************************************************************/
+    public static void chebyshevcoefficients(int n, out double[] c)
+    {
+        c = new double[0];
+        chebyshev.chebyshevcoefficients(n, ref c);
+        return;
+    }
+
+    /*************************************************************************
+    Conversion of a series of Chebyshev polynomials to a power series.
+
+    Represents A[0]*T0(x) + A[1]*T1(x) + ... + A[N]*Tn(x) as
+    B[0] + B[1]*X + ... + B[N]*X^N.
+
+    Input parameters:
+        A   -   Chebyshev series coefficients
+        N   -   degree, N>=0
+
+    Output parameters
+        B   -   power series coefficients
+    *************************************************************************/
+    public static void fromchebyshev(double[] a, int n, out double[] b)
+    {
+        b = new double[0];
+        chebyshev.fromchebyshev(a, n, ref b);
+        return;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    Chi-square distribution
+
+    Returns the area under the left hand tail (from 0 to x)
+    of the Chi square probability density function with
+    v degrees of freedom.
+
+
+                                      x
+                                       -
+                           1          | |  v/2-1  -t/2
+     P( x | v )   =   -----------     |   t      e     dt
+                       v/2  -       | |
+                      2    | (v/2)   -
+                                      0
+
+    where x is the Chi-square variable.
+
+    The incomplete gamma integral is used, according to the
+    formula
+
+    y = chdtr( v, x ) = igam( v/2.0, x/2.0 ).
+
+    The arguments must both be positive.
+
+    ACCURACY:
+
+    See incomplete gamma function
+
+
+    Cephes Math Library Release 2.8:  June, 2000
+    Copyright 1984, 1987, 2000 by Stephen L. Moshier
+    *************************************************************************/
+    public static double chisquaredistribution(double v, double x)
+    {
+
+        double result = chisquaredistr.chisquaredistribution(v, x);
+        return result;
+    }
+
+    /*************************************************************************
+    Complemented Chi-square distribution
+
+    Returns the area under the right hand tail (from x to
+    infinity) of the Chi square probability density function
+    with v degrees of freedom:
+
+                                     inf.
+                                       -
+                           1          | |  v/2-1  -t/2
+     P( x | v )   =   -----------     |   t      e     dt
+                       v/2  -       | |
+                      2    | (v/2)   -
+                                      x
+
+    where x is the Chi-square variable.
+
+    The incomplete gamma integral is used, according to the
+    formula
+
+    y = chdtr( v, x ) = igamc( v/2.0, x/2.0 ).
+
+    The arguments must both be positive.
+
+    ACCURACY:
+
+    See incomplete gamma function
+
+    Cephes Math Library Release 2.8:  June, 2000
+    Copyright 1984, 1987, 2000 by Stephen L. Moshier
+    *************************************************************************/
+    public static double chisquarecdistribution(double v, double x)
+    {
+
+        double result = chisquaredistr.chisquarecdistribution(v, x);
+        return result;
+    }
+
+    /*************************************************************************
+    Inverse of complemented Chi-square distribution
+
+    Finds the Chi-square argument x such that the integral
+    from x to infinity of the Chi-square density is equal
+    to the given cumulative probability y.
+
+    This is accomplished using the inverse gamma integral
+    function and the relation
+
+       x/2 = igami( df/2, y );
+
+    ACCURACY:
+
+    See inverse incomplete gamma function
+
+
+    Cephes Math Library Release 2.8:  June, 2000
+    Copyright 1984, 1987, 2000 by Stephen L. Moshier
+    *************************************************************************/
+    public static double invchisquaredistribution(double v, double y)
+    {
+
+        double result = chisquaredistr.invchisquaredistribution(v, y);
+        return result;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    Dawson's Integral
+
+    Approximates the integral
+
+                                x
+                                -
+                         2     | |        2
+     dawsn(x)  =  exp( -x  )   |    exp( t  ) dt
+                             | |
+                              -
+                              0
+
+    Three different rational approximations are employed, for
+    the intervals 0 to 3.25; 3.25 to 6.25; and 6.25 up.
+
+    ACCURACY:
+
+                         Relative error:
+    arithmetic   domain     # trials      peak         rms
+       IEEE      0,10        10000       6.9e-16     1.0e-16
+
+    Cephes Math Library Release 2.8:  June, 2000
+    Copyright 1984, 1987, 1989, 2000 by Stephen L. Moshier
+    *************************************************************************/
+    public static double dawsonintegral(double x)
+    {
+
+        double result = dawson.dawsonintegral(x);
+        return result;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    Complete elliptic integral of the first kind
+
+    Approximates the integral
+
+
+
+               pi/2
+                -
+               | |
+               |           dt
+    K(m)  =    |    ------------------
+               |                   2
+             | |    sqrt( 1 - m sin t )
+              -
+               0
+
+    using the approximation
+
+        P(x)  -  log x Q(x).
+
+    ACCURACY:
+
+                         Relative error:
+    arithmetic   domain     # trials      peak         rms
+       IEEE       0,1        30000       2.5e-16     6.8e-17
+
+    Cephes Math Library, Release 2.8:  June, 2000
+    Copyright 1984, 1987, 2000 by Stephen L. Moshier
+    *************************************************************************/
+    public static double ellipticintegralk(double m)
+    {
+
+        double result = elliptic.ellipticintegralk(m);
+        return result;
+    }
+
+    /*************************************************************************
+    Complete elliptic integral of the first kind
+
+    Approximates the integral
+
+
+
+               pi/2
+                -
+               | |
+               |           dt
+    K(m)  =    |    ------------------
+               |                   2
+             | |    sqrt( 1 - m sin t )
+              -
+               0
+
+    where m = 1 - m1, using the approximation
+
+        P(x)  -  log x Q(x).
+
+    The argument m1 is used rather than m so that the logarithmic
+    singularity at m = 1 will be shifted to the origin; this
+    preserves maximum accuracy.
+
+    K(0) = pi/2.
+
+    ACCURACY:
+
+                         Relative error:
+    arithmetic   domain     # trials      peak         rms
+       IEEE       0,1        30000       2.5e-16     6.8e-17
+
+    Àëãîðèòì âçÿò èç áèáëèîòåêè Cephes
+    *************************************************************************/
+    public static double ellipticintegralkhighprecision(double m1)
+    {
+
+        double result = elliptic.ellipticintegralkhighprecision(m1);
+        return result;
+    }
+
+    /*************************************************************************
+    Incomplete elliptic integral of the first kind F(phi|m)
+
+    Approximates the integral
+
+
+
+                   phi
+                    -
+                   | |
+                   |           dt
+    F(phi_\m)  =    |    ------------------
+                   |                   2
+                 | |    sqrt( 1 - m sin t )
+                  -
+                   0
+
+    of amplitude phi and modulus m, using the arithmetic -
+    geometric mean algorithm.
+
+
+
+
+    ACCURACY:
+
+    Tested at random points with m in [0, 1] and phi as indicated.
+
+                         Relative error:
+    arithmetic   domain     # trials      peak         rms
+       IEEE     -10,10       200000      7.4e-16     1.0e-16
+
+    Cephes Math Library Release 2.8:  June, 2000
+    Copyright 1984, 1987, 2000 by Stephen L. Moshier
+    *************************************************************************/
+    public static double incompleteellipticintegralk(double phi, double m)
+    {
+
+        double result = elliptic.incompleteellipticintegralk(phi, m);
+        return result;
+    }
+
+    /*************************************************************************
+    Complete elliptic integral of the second kind
+
+    Approximates the integral
+
+
+               pi/2
+                -
+               | |                 2
+    E(m)  =    |    sqrt( 1 - m sin t ) dt
+             | |
+              -
+               0
+
+    using the approximation
+
+         P(x)  -  x log x Q(x).
+
+    ACCURACY:
+
+                         Relative error:
+    arithmetic   domain     # trials      peak         rms
+       IEEE       0, 1       10000       2.1e-16     7.3e-17
+
+    Cephes Math Library, Release 2.8: June, 2000
+    Copyright 1984, 1987, 1989, 2000 by Stephen L. Moshier
+    *************************************************************************/
+    public static double ellipticintegrale(double m)
+    {
+
+        double result = elliptic.ellipticintegrale(m);
+        return result;
+    }
+
+    /*************************************************************************
+    Incomplete elliptic integral of the second kind
+
+    Approximates the integral
+
+
+                   phi
+                    -
+                   | |
+                   |                   2
+    E(phi_\m)  =    |    sqrt( 1 - m sin t ) dt
+                   |
+                 | |
+                  -
+                   0
+
+    of amplitude phi and modulus m, using the arithmetic -
+    geometric mean algorithm.
+
+    ACCURACY:
+
+    Tested at random arguments with phi in [-10, 10] and m in
+    [0, 1].
+                         Relative error:
+    arithmetic   domain     # trials      peak         rms
+       IEEE     -10,10      150000       3.3e-15     1.4e-16
+
+    Cephes Math Library Release 2.8:  June, 2000
+    Copyright 1984, 1987, 1993, 2000 by Stephen L. Moshier
+    *************************************************************************/
+    public static double incompleteellipticintegrale(double phi, double m)
+    {
+
+        double result = elliptic.incompleteellipticintegrale(phi, m);
+        return result;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    Exponential integral Ei(x)
+
+                  x
+                   -     t
+                  | |   e
+       Ei(x) =   -|-   ---  dt .
+                | |     t
+                 -
+                -inf
+
+    Not defined for x <= 0.
+    See also expn.c.
+
+
+
+    ACCURACY:
+
+                         Relative error:
+    arithmetic   domain     # trials      peak         rms
+       IEEE       0,100       50000      8.6e-16     1.3e-16
+
+    Cephes Math Library Release 2.8:  May, 1999
+    Copyright 1999 by Stephen L. Moshier
+    *************************************************************************/
+    public static double exponentialintegralei(double x)
+    {
+
+        double result = expintegrals.exponentialintegralei(x);
+        return result;
+    }
+
+    /*************************************************************************
+    Exponential integral En(x)
+
+    Evaluates the exponential integral
+
+                    inf.
+                      -
+                     | |   -xt
+                     |    e
+         E (x)  =    |    ----  dt.
+          n          |      n
+                   | |     t
+                    -
+                     1
+
+
+    Both n and x must be nonnegative.
+
+    The routine employs either a power series, a continued
+    fraction, or an asymptotic formula depending on the
+    relative values of n and x.
+
+    ACCURACY:
+
+                         Relative error:
+    arithmetic   domain     # trials      peak         rms
+       IEEE      0, 30       10000       1.7e-15     3.6e-16
+
+    Cephes Math Library Release 2.8:  June, 2000
+    Copyright 1985, 2000 by Stephen L. Moshier
+    *************************************************************************/
+    public static double exponentialintegralen(double x, int n)
+    {
+
+        double result = expintegrals.exponentialintegralen(x, n);
+        return result;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    F distribution
+
+    Returns the area from zero to x under the F density
+    function (also known as Snedcor's density or the
+    variance ratio density).  This is the density
+    of x = (u1/df1)/(u2/df2), where u1 and u2 are random
+    variables having Chi square distributions with df1
+    and df2 degrees of freedom, respectively.
+    The incomplete beta integral is used, according to the
+    formula
+
+    P(x) = incbet( df1/2, df2/2, (df1*x/(df2 + df1*x) ).
+
+
+    The arguments a and b are greater than zero, and x is
+    nonnegative.
+
+    ACCURACY:
+
+    Tested at random points (a,b,x).
+
+                   x     a,b                     Relative error:
+    arithmetic  domain  domain     # trials      peak         rms
+       IEEE      0,1    0,100       100000      9.8e-15     1.7e-15
+       IEEE      1,5    0,100       100000      6.5e-15     3.5e-16
+       IEEE      0,1    1,10000     100000      2.2e-11     3.3e-12
+       IEEE      1,5    1,10000     100000      1.1e-11     1.7e-13
+
+    Cephes Math Library Release 2.8:  June, 2000
+    Copyright 1984, 1987, 1995, 2000 by Stephen L. Moshier
+    *************************************************************************/
+    public static double fdistribution(int a, int b, double x)
+    {
+
+        double result = fdistr.fdistribution(a, b, x);
+        return result;
+    }
+
+    /*************************************************************************
+    Complemented F distribution
+
+    Returns the area from x to infinity under the F density
+    function (also known as Snedcor's density or the
+    variance ratio density).
+
+
+                         inf.
+                          -
+                 1       | |  a-1      b-1
+    1-P(x)  =  ------    |   t    (1-t)    dt
+               B(a,b)  | |
+                        -
+                         x
+
+
+    The incomplete beta integral is used, according to the
+    formula
+
+    P(x) = incbet( df2/2, df1/2, (df2/(df2 + df1*x) ).
+
+
+    ACCURACY:
+
+    Tested at random points (a,b,x) in the indicated intervals.
+                   x     a,b                     Relative error:
+    arithmetic  domain  domain     # trials      peak         rms
+       IEEE      0,1    1,100       100000      3.7e-14     5.9e-16
+       IEEE      1,5    1,100       100000      8.0e-15     1.6e-15
+       IEEE      0,1    1,10000     100000      1.8e-11     3.5e-13
+       IEEE      1,5    1,10000     100000      2.0e-11     3.0e-12
+
+    Cephes Math Library Release 2.8:  June, 2000
+    Copyright 1984, 1987, 1995, 2000 by Stephen L. Moshier
+    *************************************************************************/
+    public static double fcdistribution(int a, int b, double x)
+    {
+
+        double result = fdistr.fcdistribution(a, b, x);
+        return result;
+    }
+
+    /*************************************************************************
+    Inverse of complemented F distribution
+
+    Finds the F density argument x such that the integral
+    from x to infinity of the F density is equal to the
+    given probability p.
+
+    This is accomplished using the inverse beta integral
+    function and the relations
+
+         z = incbi( df2/2, df1/2, p )
+         x = df2 (1-z) / (df1 z).
+
+    Note: the following relations hold for the inverse of
+    the uncomplemented F distribution:
+
+         z = incbi( df1/2, df2/2, p )
+         x = df2 z / (df1 (1-z)).
+
+    ACCURACY:
+
+    Tested at random points (a,b,p).
+
+                 a,b                     Relative error:
+    arithmetic  domain     # trials      peak         rms
+     For p between .001 and 1:
+       IEEE     1,100       100000      8.3e-15     4.7e-16
+       IEEE     1,10000     100000      2.1e-11     1.4e-13
+     For p between 10^-6 and 10^-3:
+       IEEE     1,100        50000      1.3e-12     8.4e-15
+       IEEE     1,10000      50000      3.0e-12     4.8e-14
+
+    Cephes Math Library Release 2.8:  June, 2000
+    Copyright 1984, 1987, 1995, 2000 by Stephen L. Moshier
+    *************************************************************************/
+    public static double invfdistribution(int a, int b, double y)
+    {
+
+        double result = fdistr.invfdistribution(a, b, y);
+        return result;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    Fresnel integral
+
+    Evaluates the Fresnel integrals
+
+              x
+              -
+             | |
+    C(x) =   |   cos(pi/2 t**2) dt,
+           | |
+            -
+             0
+
+              x
+              -
+             | |
+    S(x) =   |   sin(pi/2 t**2) dt.
+           | |
+            -
+             0
+
+
+    The integrals are evaluated by a power series for x < 1.
+    For x >= 1 auxiliary functions f(x) and g(x) are employed
+    such that
+
+    C(x) = 0.5 + f(x) sin( pi/2 x**2 ) - g(x) cos( pi/2 x**2 )
+    S(x) = 0.5 - f(x) cos( pi/2 x**2 ) - g(x) sin( pi/2 x**2 )
+
+
+
+    ACCURACY:
+
+     Relative error.
+
+    Arithmetic  function   domain     # trials      peak         rms
+      IEEE       S(x)      0, 10       10000       2.0e-15     3.2e-16
+      IEEE       C(x)      0, 10       10000       1.8e-15     3.3e-16
+
+    Cephes Math Library Release 2.8:  June, 2000
+    Copyright 1984, 1987, 1989, 2000 by Stephen L. Moshier
+    *************************************************************************/
+    public static void fresnelintegral(double x, ref double c, ref double s)
+    {
+
+        fresnel.fresnelintegral(x, ref c, ref s);
+        return;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    Calculation of the value of the Hermite polynomial.
+
+    Parameters:
+        n   -   degree, n>=0
+        x   -   argument
+
+    Result:
+        the value of the Hermite polynomial Hn at x
+    *************************************************************************/
+    public static double hermitecalculate(int n, double x)
+    {
+
+        double result = hermite.hermitecalculate(n, x);
+        return result;
+    }
+
+    /*************************************************************************
+    Summation of Hermite polynomials using Clenshaws recurrence formula.
+
+    This routine calculates
+        c[0]*H0(x) + c[1]*H1(x) + ... + c[N]*HN(x)
+
+    Parameters:
+        n   -   degree, n>=0
+        x   -   argument
+
+    Result:
+        the value of the Hermite polynomial at x
+    *************************************************************************/
+    public static double hermitesum(double[] c, int n, double x)
+    {
+
+        double result = hermite.hermitesum(c, n, x);
+        return result;
+    }
+
+    /*************************************************************************
+    Representation of Hn as C[0] + C[1]*X + ... + C[N]*X^N
+
+    Input parameters:
+        N   -   polynomial degree, n>=0
+
+    Output parameters:
+        C   -   coefficients
+    *************************************************************************/
+    public static void hermitecoefficients(int n, out double[] c)
+    {
+        c = new double[0];
+        hermite.hermitecoefficients(n, ref c);
+        return;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    Jacobian Elliptic Functions
+
+    Evaluates the Jacobian elliptic functions sn(u|m), cn(u|m),
+    and dn(u|m) of parameter m between 0 and 1, and real
+    argument u.
+
+    These functions are periodic, with quarter-period on the
+    real axis equal to the complete elliptic integral
+    ellpk(1.0-m).
+
+    Relation to incomplete elliptic integral:
+    If u = ellik(phi,m), then sn(u|m) = sin(phi),
+    and cn(u|m) = cos(phi).  Phi is called the amplitude of u.
+
+    Computation is by means of the arithmetic-geometric mean
+    algorithm, except when m is within 1e-9 of 0 or 1.  In the
+    latter case with m close to 1, the approximation applies
+    only for phi < pi/2.
+
+    ACCURACY:
+
+    Tested at random points with u between 0 and 10, m between
+    0 and 1.
+
+               Absolute error (* = relative error):
+    arithmetic   function   # trials      peak         rms
+       IEEE      phi         10000       9.2e-16*    1.4e-16*
+       IEEE      sn          50000       4.1e-15     4.6e-16
+       IEEE      cn          40000       3.6e-15     4.4e-16
+       IEEE      dn          10000       1.3e-12     1.8e-14
+
+     Peak error observed in consistency check using addition
+    theorem for sn(u+v) was 4e-16 (absolute).  Also tested by
+    the above relation to the incomplete elliptic integral.
+    Accuracy deteriorates when u is large.
+
+    Cephes Math Library Release 2.8:  June, 2000
+    Copyright 1984, 1987, 2000 by Stephen L. Moshier
+    *************************************************************************/
+    public static void jacobianellipticfunctions(double u, double m, out double sn, out double cn, out double dn, out double ph)
+    {
+        sn = 0;
+        cn = 0;
+        dn = 0;
+        ph = 0;
+        jacobianelliptic.jacobianellipticfunctions(u, m, ref sn, ref cn, ref dn, ref ph);
+        return;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    Calculation of the value of the Laguerre polynomial.
+
+    Parameters:
+        n   -   degree, n>=0
+        x   -   argument
+
+    Result:
+        the value of the Laguerre polynomial Ln at x
+    *************************************************************************/
+    public static double laguerrecalculate(int n, double x)
+    {
+
+        double result = laguerre.laguerrecalculate(n, x);
+        return result;
+    }
+
+    /*************************************************************************
+    Summation of Laguerre polynomials using Clenshaws recurrence formula.
+
+    This routine calculates c[0]*L0(x) + c[1]*L1(x) + ... + c[N]*LN(x)
+
+    Parameters:
+        n   -   degree, n>=0
+        x   -   argument
+
+    Result:
+        the value of the Laguerre polynomial at x
+    *************************************************************************/
+    public static double laguerresum(double[] c, int n, double x)
+    {
+
+        double result = laguerre.laguerresum(c, n, x);
+        return result;
+    }
+
+    /*************************************************************************
+    Representation of Ln as C[0] + C[1]*X + ... + C[N]*X^N
+
+    Input parameters:
+        N   -   polynomial degree, n>=0
+
+    Output parameters:
+        C   -   coefficients
+    *************************************************************************/
+    public static void laguerrecoefficients(int n, out double[] c)
+    {
+        c = new double[0];
+        laguerre.laguerrecoefficients(n, ref c);
+        return;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    Calculation of the value of the Legendre polynomial Pn.
+
+    Parameters:
+        n   -   degree, n>=0
+        x   -   argument
+
+    Result:
+        the value of the Legendre polynomial Pn at x
+    *************************************************************************/
+    public static double legendrecalculate(int n, double x)
+    {
+
+        double result = legendre.legendrecalculate(n, x);
+        return result;
+    }
+
+    /*************************************************************************
+    Summation of Legendre polynomials using Clenshaws recurrence formula.
+
+    This routine calculates
+        c[0]*P0(x) + c[1]*P1(x) + ... + c[N]*PN(x)
+
+    Parameters:
+        n   -   degree, n>=0
+        x   -   argument
+
+    Result:
+        the value of the Legendre polynomial at x
+    *************************************************************************/
+    public static double legendresum(double[] c, int n, double x)
+    {
+
+        double result = legendre.legendresum(c, n, x);
+        return result;
+    }
+
+    /*************************************************************************
+    Representation of Pn as C[0] + C[1]*X + ... + C[N]*X^N
+
+    Input parameters:
+        N   -   polynomial degree, n>=0
+
+    Output parameters:
+        C   -   coefficients
+    *************************************************************************/
+    public static void legendrecoefficients(int n, out double[] c)
+    {
+        c = new double[0];
+        legendre.legendrecoefficients(n, ref c);
+        return;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    Poisson distribution
+
+    Returns the sum of the first k+1 terms of the Poisson
+    distribution:
+
+      k         j
+      --   -m  m
+      >   e    --
+      --       j!
+     j=0
+
+    The terms are not summed directly; instead the incomplete
+    gamma integral is employed, according to the relation
+
+    y = pdtr( k, m ) = igamc( k+1, m ).
+
+    The arguments must both be positive.
+    ACCURACY:
+
+    See incomplete gamma function
+
+    Cephes Math Library Release 2.8:  June, 2000
+    Copyright 1984, 1987, 1995, 2000 by Stephen L. Moshier
+    *************************************************************************/
+    public static double poissondistribution(int k, double m)
+    {
+
+        double result = poissondistr.poissondistribution(k, m);
+        return result;
+    }
+
+    /*************************************************************************
+    Complemented Poisson distribution
+
+    Returns the sum of the terms k+1 to infinity of the Poisson
+    distribution:
+
+     inf.       j
+      --   -m  m
+      >   e    --
+      --       j!
+     j=k+1
+
+    The terms are not summed directly; instead the incomplete
+    gamma integral is employed, according to the formula
+
+    y = pdtrc( k, m ) = igam( k+1, m ).
+
+    The arguments must both be positive.
+
+    ACCURACY:
+
+    See incomplete gamma function
+
+    Cephes Math Library Release 2.8:  June, 2000
+    Copyright 1984, 1987, 1995, 2000 by Stephen L. Moshier
+    *************************************************************************/
+    public static double poissoncdistribution(int k, double m)
+    {
+
+        double result = poissondistr.poissoncdistribution(k, m);
+        return result;
+    }
+
+    /*************************************************************************
+    Inverse Poisson distribution
+
+    Finds the Poisson variable x such that the integral
+    from 0 to x of the Poisson density is equal to the
+    given probability y.
+
+    This is accomplished using the inverse gamma integral
+    function and the relation
+
+       m = igami( k+1, y ).
+
+    ACCURACY:
+
+    See inverse incomplete gamma function
+
+    Cephes Math Library Release 2.8:  June, 2000
+    Copyright 1984, 1987, 1995, 2000 by Stephen L. Moshier
+    *************************************************************************/
+    public static double invpoissondistribution(int k, double y)
+    {
+
+        double result = poissondistr.invpoissondistribution(k, y);
+        return result;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    Psi (digamma) function
+
+                 d      -
+      psi(x)  =  -- ln | (x)
+                 dx
+
+    is the logarithmic derivative of the gamma function.
+    For integer x,
+                      n-1
+                       -
+    psi(n) = -EUL  +   >  1/k.
+                       -
+                      k=1
+
+    This formula is used for 0 < n <= 10.  If x is negative, it
+    is transformed to a positive argument by the reflection
+    formula  psi(1-x) = psi(x) + pi cot(pi x).
+    For general positive x, the argument is made greater than 10
+    using the recurrence  psi(x+1) = psi(x) + 1/x.
+    Then the following asymptotic expansion is applied:
+
+                              inf.   B
+                               -      2k
+    psi(x) = log(x) - 1/2x -   >   -------
+                               -        2k
+                              k=1   2k x
+
+    where the B2k are Bernoulli numbers.
+
+    ACCURACY:
+       Relative error (except absolute when |psi| < 1):
+    arithmetic   domain     # trials      peak         rms
+       IEEE      0,30        30000       1.3e-15     1.4e-16
+       IEEE      -30,0       40000       1.5e-15     2.2e-16
+
+    Cephes Math Library Release 2.8:  June, 2000
+    Copyright 1984, 1987, 1992, 2000 by Stephen L. Moshier
+    *************************************************************************/
+    public static double psi(double x)
+    {
+
+        double result = psif.psi(x);
+        return result;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    Student's t distribution
+
+    Computes the integral from minus infinity to t of the Student
+    t distribution with integer k > 0 degrees of freedom:
+
+                                         t
+                                         -
+                                        | |
+                 -                      |         2   -(k+1)/2
+                | ( (k+1)/2 )           |  (     x   )
+          ----------------------        |  ( 1 + --- )        dx
+                        -               |  (      k  )
+          sqrt( k pi ) | ( k/2 )        |
+                                      | |
+                                       -
+                                      -inf.
+
+    Relation to incomplete beta integral:
+
+           1 - stdtr(k,t) = 0.5 * incbet( k/2, 1/2, z )
+    where
+           z = k/(k + t**2).
+
+    For t < -2, this is the method of computation.  For higher t,
+    a direct method is derived from integration by parts.
+    Since the function is symmetric about t=0, the area under the
+    right tail of the density is found by calling the function
+    with -t instead of t.
+
+    ACCURACY:
+
+    Tested at random 1 <= k <= 25.  The "domain" refers to t.
+                         Relative error:
+    arithmetic   domain     # trials      peak         rms
+       IEEE     -100,-2      50000       5.9e-15     1.4e-15
+       IEEE     -2,100      500000       2.7e-15     4.9e-17
+
+    Cephes Math Library Release 2.8:  June, 2000
+    Copyright 1984, 1987, 1995, 2000 by Stephen L. Moshier
+    *************************************************************************/
+    public static double studenttdistribution(int k, double t)
+    {
+
+        double result = studenttdistr.studenttdistribution(k, t);
+        return result;
+    }
+
+    /*************************************************************************
+    Functional inverse of Student's t distribution
+
+    Given probability p, finds the argument t such that stdtr(k,t)
+    is equal to p.
+
+    ACCURACY:
+
+    Tested at random 1 <= k <= 100.  The "domain" refers to p:
+                         Relative error:
+    arithmetic   domain     # trials      peak         rms
+       IEEE    .001,.999     25000       5.7e-15     8.0e-16
+       IEEE    10^-6,.001    25000       2.0e-12     2.9e-14
+
+    Cephes Math Library Release 2.8:  June, 2000
+    Copyright 1984, 1987, 1995, 2000 by Stephen L. Moshier
+    *************************************************************************/
+    public static double invstudenttdistribution(int k, double p)
+    {
+
+        double result = studenttdistr.invstudenttdistribution(k, p);
+        return result;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    Sine and cosine integrals
+
+    Evaluates the integrals
+
+                             x
+                             -
+                            |  cos t - 1
+      Ci(x) = eul + ln x +  |  --------- dt,
+                            |      t
+                           -
+                            0
+                x
+                -
+               |  sin t
+      Si(x) =  |  ----- dt
+               |    t
+              -
+               0
+
+    where eul = 0.57721566490153286061 is Euler's constant.
+    The integrals are approximated by rational functions.
+    For x > 8 auxiliary functions f(x) and g(x) are employed
+    such that
+
+    Ci(x) = f(x) sin(x) - g(x) cos(x)
+    Si(x) = pi/2 - f(x) cos(x) - g(x) sin(x)
+
+
+    ACCURACY:
+       Test interval = [0,50].
+    Absolute error, except relative when > 1:
+    arithmetic   function   # trials      peak         rms
+       IEEE        Si        30000       4.4e-16     7.3e-17
+       IEEE        Ci        30000       6.9e-16     5.1e-17
+
+    Cephes Math Library Release 2.1:  January, 1989
+    Copyright 1984, 1987, 1989 by Stephen L. Moshier
+    *************************************************************************/
+    public static void sinecosineintegrals(double x, out double si, out double ci)
+    {
+        si = 0;
+        ci = 0;
+        trigintegrals.sinecosineintegrals(x, ref si, ref ci);
+        return;
+    }
+
+    /*************************************************************************
+    Hyperbolic sine and cosine integrals
+
+    Approximates the integrals
+
+                               x
+                               -
+                              | |   cosh t - 1
+      Chi(x) = eul + ln x +   |    -----------  dt,
+                            | |          t
+                             -
+                             0
+
+                  x
+                  -
+                 | |  sinh t
+      Shi(x) =   |    ------  dt
+               | |       t
+                -
+                0
+
+    where eul = 0.57721566490153286061 is Euler's constant.
+    The integrals are evaluated by power series for x < 8
+    and by Chebyshev expansions for x between 8 and 88.
+    For large x, both functions approach exp(x)/2x.
+    Arguments greater than 88 in magnitude return MAXNUM.
+
+
+    ACCURACY:
+
+    Test interval 0 to 88.
+                         Relative error:
+    arithmetic   function  # trials      peak         rms
+       IEEE         Shi      30000       6.9e-16     1.6e-16
+           Absolute error, except relative when |Chi| > 1:
+       IEEE         Chi      30000       8.4e-16     1.4e-16
+
+    Cephes Math Library Release 2.8:  June, 2000
+    Copyright 1984, 1987, 2000 by Stephen L. Moshier
+    *************************************************************************/
+    public static void hyperbolicsinecosineintegrals(double x, out double shi, out double chi)
+    {
+        shi = 0;
+        chi = 0;
+        trigintegrals.hyperbolicsinecosineintegrals(x, ref shi, ref chi);
+        return;
+    }
+
+}
+public partial class alglib
+{
+    public class gammafunc
+    {
+        /*************************************************************************
+        Gamma function
+
+        Input parameters:
+            X   -   argument
+
+        Domain:
+            0 < X < 171.6
+            -170 < X < 0, X is not an integer.
+
+        Relative error:
+         arithmetic   domain     # trials      peak         rms
+            IEEE    -170,-33      20000       2.3e-15     3.3e-16
+            IEEE     -33,  33     20000       9.4e-16     2.2e-16
+            IEEE      33, 171.6   20000       2.3e-15     3.2e-16
+
+        Cephes Math Library Release 2.8:  June, 2000
+        Original copyright 1984, 1987, 1989, 1992, 2000 by Stephen L. Moshier
+        Translated to AlgoPascal by Bochkanov Sergey (2005, 2006, 2007).
+        *************************************************************************/
+        public static double gammafunction(double x)
+        {
+            double result = 0;
+            double p = 0;
+            double pp = 0;
+            double q = 0;
+            double qq = 0;
+            double z = 0;
+            int i = 0;
+            double sgngam = 0;
+
+            sgngam = 1;
+            q = Math.Abs(x);
+            if( (double)(q)>(double)(33.0) )
+            {
+                if( (double)(x)<(double)(0.0) )
+                {
+                    p = (int)Math.Floor(q);
+                    i = (int)Math.Round(p);
+                    if( i%2==0 )
+                    {
+                        sgngam = -1;
+                    }
+                    z = q-p;
+                    if( (double)(z)>(double)(0.5) )
+                    {
+                        p = p+1;
+                        z = q-p;
+                    }
+                    z = q*Math.Sin(Math.PI*z);
+                    z = Math.Abs(z);
+                    z = Math.PI/(z*gammastirf(q));
+                }
+                else
+                {
+                    z = gammastirf(x);
+                }
+                result = sgngam*z;
+                return result;
+            }
+            z = 1;
+            while( (double)(x)>=(double)(3) )
+            {
+                x = x-1;
+                z = z*x;
+            }
+            while( (double)(x)<(double)(0) )
+            {
+                if( (double)(x)>(double)(-0.000000001) )
+                {
+                    result = z/((1+0.5772156649015329*x)*x);
+                    return result;
+                }
+                z = z/x;
+                x = x+1;
+            }
+            while( (double)(x)<(double)(2) )
+            {
+                if( (double)(x)<(double)(0.000000001) )
+                {
+                    result = z/((1+0.5772156649015329*x)*x);
+                    return result;
+                }
+                z = z/x;
+                x = x+1.0;
+            }
+            if( (double)(x)==(double)(2) )
+            {
+                result = z;
+                return result;
+            }
+            x = x-2.0;
+            pp = 1.60119522476751861407E-4;
+            pp = 1.19135147006586384913E-3+x*pp;
+            pp = 1.04213797561761569935E-2+x*pp;
+            pp = 4.76367800457137231464E-2+x*pp;
+            pp = 2.07448227648435975150E-1+x*pp;
+            pp = 4.94214826801497100753E-1+x*pp;
+            pp = 9.99999999999999996796E-1+x*pp;
+            qq = -2.31581873324120129819E-5;
+            qq = 5.39605580493303397842E-4+x*qq;
+            qq = -4.45641913851797240494E-3+x*qq;
+            qq = 1.18139785222060435552E-2+x*qq;
+            qq = 3.58236398605498653373E-2+x*qq;
+            qq = -2.34591795718243348568E-1+x*qq;
+            qq = 7.14304917030273074085E-2+x*qq;
+            qq = 1.00000000000000000320+x*qq;
+            result = z*pp/qq;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Natural logarithm of gamma function
+
+        Input parameters:
+            X       -   argument
+
+        Result:
+            logarithm of the absolute value of the Gamma(X).
+
+        Output parameters:
+            SgnGam  -   sign(Gamma(X))
+
+        Domain:
+            0 < X < 2.55e305
+            -2.55e305 < X < 0, X is not an integer.
+
+        ACCURACY:
+        arithmetic      domain        # trials     peak         rms
+           IEEE    0, 3                 28000     5.4e-16     1.1e-16
+           IEEE    2.718, 2.556e305     40000     3.5e-16     8.3e-17
+        The error criterion was relative when the function magnitude
+        was greater than one but absolute when it was less than one.
+
+        The following test used the relative error criterion, though
+        at certain points the relative error could be much higher than
+        indicated.
+           IEEE    -200, -4             10000     4.8e-16     1.3e-16
+
+        Cephes Math Library Release 2.8:  June, 2000
+        Copyright 1984, 1987, 1989, 1992, 2000 by Stephen L. Moshier
+        Translated to AlgoPascal by Bochkanov Sergey (2005, 2006, 2007).
+        *************************************************************************/
+        public static double lngamma(double x,
+            ref double sgngam)
+        {
+            double result = 0;
+            double a = 0;
+            double b = 0;
+            double c = 0;
+            double p = 0;
+            double q = 0;
+            double u = 0;
+            double w = 0;
+            double z = 0;
+            int i = 0;
+            double logpi = 0;
+            double ls2pi = 0;
+            double tmp = 0;
+
+            sgngam = 0;
+
+            sgngam = 1;
+            logpi = 1.14472988584940017414;
+            ls2pi = 0.91893853320467274178;
+            if( (double)(x)<(double)(-34.0) )
+            {
+                q = -x;
+                w = lngamma(q, ref tmp);
+                p = (int)Math.Floor(q);
+                i = (int)Math.Round(p);
+                if( i%2==0 )
+                {
+                    sgngam = -1;
+                }
+                else
+                {
+                    sgngam = 1;
+                }
+                z = q-p;
+                if( (double)(z)>(double)(0.5) )
+                {
+                    p = p+1;
+                    z = p-q;
+                }
+                z = q*Math.Sin(Math.PI*z);
+                result = logpi-Math.Log(z)-w;
+                return result;
+            }
+            if( (double)(x)<(double)(13) )
+            {
+                z = 1;
+                p = 0;
+                u = x;
+                while( (double)(u)>=(double)(3) )
+                {
+                    p = p-1;
+                    u = x+p;
+                    z = z*u;
+                }
+                while( (double)(u)<(double)(2) )
+                {
+                    z = z/u;
+                    p = p+1;
+                    u = x+p;
+                }
+                if( (double)(z)<(double)(0) )
+                {
+                    sgngam = -1;
+                    z = -z;
+                }
+                else
+                {
+                    sgngam = 1;
+                }
+                if( (double)(u)==(double)(2) )
+                {
+                    result = Math.Log(z);
+                    return result;
+                }
+                p = p-2;
+                x = x+p;
+                b = -1378.25152569120859100;
+                b = -38801.6315134637840924+x*b;
+                b = -331612.992738871184744+x*b;
+                b = -1162370.97492762307383+x*b;
+                b = -1721737.00820839662146+x*b;
+                b = -853555.664245765465627+x*b;
+                c = 1;
+                c = -351.815701436523470549+x*c;
+                c = -17064.2106651881159223+x*c;
+                c = -220528.590553854454839+x*c;
+                c = -1139334.44367982507207+x*c;
+                c = -2532523.07177582951285+x*c;
+                c = -2018891.41433532773231+x*c;
+                p = x*b/c;
+                result = Math.Log(z)+p;
+                return result;
+            }
+            q = (x-0.5)*Math.Log(x)-x+ls2pi;
+            if( (double)(x)>(double)(100000000) )
+            {
+                result = q;
+                return result;
+            }
+            p = 1/(x*x);
+            if( (double)(x)>=(double)(1000.0) )
+            {
+                q = q+((7.9365079365079365079365*0.0001*p-2.7777777777777777777778*0.001)*p+0.0833333333333333333333)/x;
+            }
+            else
+            {
+                a = 8.11614167470508450300*0.0001;
+                a = -(5.95061904284301438324*0.0001)+p*a;
+                a = 7.93650340457716943945*0.0001+p*a;
+                a = -(2.77777777730099687205*0.001)+p*a;
+                a = 8.33333333333331927722*0.01+p*a;
+                q = q+a/x;
+            }
+            result = q;
+            return result;
+        }
+
+
+        private static double gammastirf(double x)
+        {
+            double result = 0;
+            double y = 0;
+            double w = 0;
+            double v = 0;
+            double stir = 0;
+
+            w = 1/x;
+            stir = 7.87311395793093628397E-4;
+            stir = -2.29549961613378126380E-4+w*stir;
+            stir = -2.68132617805781232825E-3+w*stir;
+            stir = 3.47222221605458667310E-3+w*stir;
+            stir = 8.33333333333482257126E-2+w*stir;
+            w = 1+w*stir;
+            y = Math.Exp(x);
+            if( (double)(x)>(double)(143.01608) )
+            {
+                v = Math.Pow(x, 0.5*x-0.25);
+                y = v*(v/y);
+            }
+            else
+            {
+                y = Math.Pow(x, x-0.5)/y;
+            }
+            result = 2.50662827463100050242*y*w;
+            return result;
+        }
+
+
+    }
+    public class normaldistr
+    {
+        /*************************************************************************
+        Error function
+
+        The integral is
+
+                                  x
+                                   -
+                        2         | |          2
+          erf(x)  =  --------     |    exp( - t  ) dt.
+                     sqrt(pi)   | |
+                                 -
+                                  0
+
+        For 0 <= |x| < 1, erf(x) = x * P4(x**2)/Q5(x**2); otherwise
+        erf(x) = 1 - erfc(x).
+
+
+        ACCURACY:
+
+                             Relative error:
+        arithmetic   domain     # trials      peak         rms
+           IEEE      0,1         30000       3.7e-16     1.0e-16
+
+        Cephes Math Library Release 2.8:  June, 2000
+        Copyright 1984, 1987, 1988, 1992, 2000 by Stephen L. Moshier
+        *************************************************************************/
+        public static double errorfunction(double x)
+        {
+            double result = 0;
+            double xsq = 0;
+            double s = 0;
+            double p = 0;
+            double q = 0;
+
+            s = Math.Sign(x);
+            x = Math.Abs(x);
+            if( (double)(x)<(double)(0.5) )
+            {
+                xsq = x*x;
+                p = 0.007547728033418631287834;
+                p = 0.288805137207594084924010+xsq*p;
+                p = 14.3383842191748205576712+xsq*p;
+                p = 38.0140318123903008244444+xsq*p;
+                p = 3017.82788536507577809226+xsq*p;
+                p = 7404.07142710151470082064+xsq*p;
+                p = 80437.3630960840172832162+xsq*p;
+                q = 0.0;
+                q = 1.00000000000000000000000+xsq*q;
+                q = 38.0190713951939403753468+xsq*q;
+                q = 658.070155459240506326937+xsq*q;
+                q = 6379.60017324428279487120+xsq*q;
+                q = 34216.5257924628539769006+xsq*q;
+                q = 80437.3630960840172826266+xsq*q;
+                result = s*1.1283791670955125738961589031*x*p/q;
+                return result;
+            }
+            if( (double)(x)>=(double)(10) )
+            {
+                result = s;
+                return result;
+            }
+            result = s*(1-errorfunctionc(x));
+            return result;
+        }
+
+
+        /*************************************************************************
+        Complementary error function
+
+         1 - erf(x) =
+
+                                  inf.
+                                    -
+                         2         | |          2
+          erfc(x)  =  --------     |    exp( - t  ) dt
+                      sqrt(pi)   | |
+                                  -
+                                   x
+
+
+        For small x, erfc(x) = 1 - erf(x); otherwise rational
+        approximations are computed.
+
+
+        ACCURACY:
+
+                             Relative error:
+        arithmetic   domain     # trials      peak         rms
+           IEEE      0,26.6417   30000       5.7e-14     1.5e-14
+
+        Cephes Math Library Release 2.8:  June, 2000
+        Copyright 1984, 1987, 1988, 1992, 2000 by Stephen L. Moshier
+        *************************************************************************/
+        public static double errorfunctionc(double x)
+        {
+            double result = 0;
+            double p = 0;
+            double q = 0;
+
+            if( (double)(x)<(double)(0) )
+            {
+                result = 2-errorfunctionc(-x);
+                return result;
+            }
+            if( (double)(x)<(double)(0.5) )
+            {
+                result = 1.0-errorfunction(x);
+                return result;
+            }
+            if( (double)(x)>=(double)(10) )
+            {
+                result = 0;
+                return result;
+            }
+            p = 0.0;
+            p = 0.5641877825507397413087057563+x*p;
+            p = 9.675807882987265400604202961+x*p;
+            p = 77.08161730368428609781633646+x*p;
+            p = 368.5196154710010637133875746+x*p;
+            p = 1143.262070703886173606073338+x*p;
+            p = 2320.439590251635247384768711+x*p;
+            p = 2898.0293292167655611275846+x*p;
+            p = 1826.3348842295112592168999+x*p;
+            q = 1.0;
+            q = 17.14980943627607849376131193+x*q;
+            q = 137.1255960500622202878443578+x*q;
+            q = 661.7361207107653469211984771+x*q;
+            q = 2094.384367789539593790281779+x*q;
+            q = 4429.612803883682726711528526+x*q;
+            q = 6089.5424232724435504633068+x*q;
+            q = 4958.82756472114071495438422+x*q;
+            q = 1826.3348842295112595576438+x*q;
+            result = Math.Exp(-math.sqr(x))*p/q;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Normal distribution function
+
+        Returns the area under the Gaussian probability density
+        function, integrated from minus infinity to x:
+
+                                   x
+                                    -
+                          1        | |          2
+           ndtr(x)  = ---------    |    exp( - t /2 ) dt
+                      sqrt(2pi)  | |
+                                  -
+                                 -inf.
+
+                    =  ( 1 + erf(z) ) / 2
+                    =  erfc(z) / 2
+
+        where z = x/sqrt(2). Computation is via the functions
+        erf and erfc.
+
+
+        ACCURACY:
+
+                             Relative error:
+        arithmetic   domain     # trials      peak         rms
+           IEEE     -13,0        30000       3.4e-14     6.7e-15
+
+        Cephes Math Library Release 2.8:  June, 2000
+        Copyright 1984, 1987, 1988, 1992, 2000 by Stephen L. Moshier
+        *************************************************************************/
+        public static double normaldistribution(double x)
+        {
+            double result = 0;
+
+            result = 0.5*(errorfunction(x/1.41421356237309504880)+1);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Inverse of the error function
+
+        Cephes Math Library Release 2.8:  June, 2000
+        Copyright 1984, 1987, 1988, 1992, 2000 by Stephen L. Moshier
+        *************************************************************************/
+        public static double inverf(double e)
+        {
+            double result = 0;
+
+            result = invnormaldistribution(0.5*(e+1))/Math.Sqrt(2);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Inverse of Normal distribution function
+
+        Returns the argument, x, for which the area under the
+        Gaussian probability density function (integrated from
+        minus infinity to x) is equal to y.
+
+
+        For small arguments 0 < y < exp(-2), the program computes
+        z = sqrt( -2.0 * log(y) );  then the approximation is
+        x = z - log(z)/z  - (1/z) P(1/z) / Q(1/z).
+        There are two rational functions P/Q, one for 0 < y < exp(-32)
+        and the other for y up to exp(-2).  For larger arguments,
+        w = y - 0.5, and  x/sqrt(2pi) = w + w**3 R(w**2)/S(w**2)).
+
+        ACCURACY:
+
+                             Relative error:
+        arithmetic   domain        # trials      peak         rms
+           IEEE     0.125, 1        20000       7.2e-16     1.3e-16
+           IEEE     3e-308, 0.135   50000       4.6e-16     9.8e-17
+
+        Cephes Math Library Release 2.8:  June, 2000
+        Copyright 1984, 1987, 1988, 1992, 2000 by Stephen L. Moshier
+        *************************************************************************/
+        public static double invnormaldistribution(double y0)
+        {
+            double result = 0;
+            double expm2 = 0;
+            double s2pi = 0;
+            double x = 0;
+            double y = 0;
+            double z = 0;
+            double y2 = 0;
+            double x0 = 0;
+            double x1 = 0;
+            int code = 0;
+            double p0 = 0;
+            double q0 = 0;
+            double p1 = 0;
+            double q1 = 0;
+            double p2 = 0;
+            double q2 = 0;
+
+            expm2 = 0.13533528323661269189;
+            s2pi = 2.50662827463100050242;
+            if( (double)(y0)<=(double)(0) )
+            {
+                result = -math.maxrealnumber;
+                return result;
+            }
+            if( (double)(y0)>=(double)(1) )
+            {
+                result = math.maxrealnumber;
+                return result;
+            }
+            code = 1;
+            y = y0;
+            if( (double)(y)>(double)(1.0-expm2) )
+            {
+                y = 1.0-y;
+                code = 0;
+            }
+            if( (double)(y)>(double)(expm2) )
+            {
+                y = y-0.5;
+                y2 = y*y;
+                p0 = -59.9633501014107895267;
+                p0 = 98.0010754185999661536+y2*p0;
+                p0 = -56.6762857469070293439+y2*p0;
+                p0 = 13.9312609387279679503+y2*p0;
+                p0 = -1.23916583867381258016+y2*p0;
+                q0 = 1;
+                q0 = 1.95448858338141759834+y2*q0;
+                q0 = 4.67627912898881538453+y2*q0;
+                q0 = 86.3602421390890590575+y2*q0;
+                q0 = -225.462687854119370527+y2*q0;
+                q0 = 200.260212380060660359+y2*q0;
+                q0 = -82.0372256168333339912+y2*q0;
+                q0 = 15.9056225126211695515+y2*q0;
+                q0 = -1.18331621121330003142+y2*q0;
+                x = y+y*y2*p0/q0;
+                x = x*s2pi;
+                result = x;
+                return result;
+            }
+            x = Math.Sqrt(-(2.0*Math.Log(y)));
+            x0 = x-Math.Log(x)/x;
+            z = 1.0/x;
+            if( (double)(x)<(double)(8.0) )
+            {
+                p1 = 4.05544892305962419923;
+                p1 = 31.5251094599893866154+z*p1;
+                p1 = 57.1628192246421288162+z*p1;
+                p1 = 44.0805073893200834700+z*p1;
+                p1 = 14.6849561928858024014+z*p1;
+                p1 = 2.18663306850790267539+z*p1;
+                p1 = -(1.40256079171354495875*0.1)+z*p1;
+                p1 = -(3.50424626827848203418*0.01)+z*p1;
+                p1 = -(8.57456785154685413611*0.0001)+z*p1;
+                q1 = 1;
+                q1 = 15.7799883256466749731+z*q1;
+                q1 = 45.3907635128879210584+z*q1;
+                q1 = 41.3172038254672030440+z*q1;
+                q1 = 15.0425385692907503408+z*q1;
+                q1 = 2.50464946208309415979+z*q1;
+                q1 = -(1.42182922854787788574*0.1)+z*q1;
+                q1 = -(3.80806407691578277194*0.01)+z*q1;
+                q1 = -(9.33259480895457427372*0.0001)+z*q1;
+                x1 = z*p1/q1;
+            }
+            else
+            {
+                p2 = 3.23774891776946035970;
+                p2 = 6.91522889068984211695+z*p2;
+                p2 = 3.93881025292474443415+z*p2;
+                p2 = 1.33303460815807542389+z*p2;
+                p2 = 2.01485389549179081538*0.1+z*p2;
+                p2 = 1.23716634817820021358*0.01+z*p2;
+                p2 = 3.01581553508235416007*0.0001+z*p2;
+                p2 = 2.65806974686737550832*0.000001+z*p2;
+                p2 = 6.23974539184983293730*0.000000001+z*p2;
+                q2 = 1;
+                q2 = 6.02427039364742014255+z*q2;
+                q2 = 3.67983563856160859403+z*q2;
+                q2 = 1.37702099489081330271+z*q2;
+                q2 = 2.16236993594496635890*0.1+z*q2;
+                q2 = 1.34204006088543189037*0.01+z*q2;
+                q2 = 3.28014464682127739104*0.0001+z*q2;
+                q2 = 2.89247864745380683936*0.000001+z*q2;
+                q2 = 6.79019408009981274425*0.000000001+z*q2;
+                x1 = z*p2/q2;
+            }
+            x = x0-x1;
+            if( code!=0 )
+            {
+                x = -x;
+            }
+            result = x;
+            return result;
+        }
+
+
+    }
+    public class igammaf
+    {
+        /*************************************************************************
+        Incomplete gamma integral
+
+        The function is defined by
+
+                                  x
+                                   -
+                          1       | |  -t  a-1
+         igam(a,x)  =   -----     |   e   t   dt.
+                         -      | |
+                        | (a)    -
+                                  0
+
+
+        In this implementation both arguments must be positive.
+        The integral is evaluated by either a power series or
+        continued fraction expansion, depending on the relative
+        values of a and x.
+
+        ACCURACY:
+
+                             Relative error:
+        arithmetic   domain     # trials      peak         rms
+           IEEE      0,30       200000       3.6e-14     2.9e-15
+           IEEE      0,100      300000       9.9e-14     1.5e-14
+
+        Cephes Math Library Release 2.8:  June, 2000
+        Copyright 1985, 1987, 2000 by Stephen L. Moshier
+        *************************************************************************/
+        public static double incompletegamma(double a,
+            double x)
+        {
+            double result = 0;
+            double igammaepsilon = 0;
+            double ans = 0;
+            double ax = 0;
+            double c = 0;
+            double r = 0;
+            double tmp = 0;
+
+            igammaepsilon = 0.000000000000001;
+            if( (double)(x)<=(double)(0) || (double)(a)<=(double)(0) )
+            {
+                result = 0;
+                return result;
+            }
+            if( (double)(x)>(double)(1) && (double)(x)>(double)(a) )
+            {
+                result = 1-incompletegammac(a, x);
+                return result;
+            }
+            ax = a*Math.Log(x)-x-gammafunc.lngamma(a, ref tmp);
+            if( (double)(ax)<(double)(-709.78271289338399) )
+            {
+                result = 0;
+                return result;
+            }
+            ax = Math.Exp(ax);
+            r = a;
+            c = 1;
+            ans = 1;
+            do
+            {
+                r = r+1;
+                c = c*x/r;
+                ans = ans+c;
+            }
+            while( (double)(c/ans)>(double)(igammaepsilon) );
+            result = ans*ax/a;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Complemented incomplete gamma integral
+
+        The function is defined by
+
+
+         igamc(a,x)   =   1 - igam(a,x)
+
+                                   inf.
+                                     -
+                            1       | |  -t  a-1
+                      =   -----     |   e   t   dt.
+                           -      | |
+                          | (a)    -
+                                    x
+
+
+        In this implementation both arguments must be positive.
+        The integral is evaluated by either a power series or
+        continued fraction expansion, depending on the relative
+        values of a and x.
+
+        ACCURACY:
+
+        Tested at random a, x.
+                       a         x                      Relative error:
+        arithmetic   domain   domain     # trials      peak         rms
+           IEEE     0.5,100   0,100      200000       1.9e-14     1.7e-15
+           IEEE     0.01,0.5  0,100      200000       1.4e-13     1.6e-15
+
+        Cephes Math Library Release 2.8:  June, 2000
+        Copyright 1985, 1987, 2000 by Stephen L. Moshier
+        *************************************************************************/
+        public static double incompletegammac(double a,
+            double x)
+        {
+            double result = 0;
+            double igammaepsilon = 0;
+            double igammabignumber = 0;
+            double igammabignumberinv = 0;
+            double ans = 0;
+            double ax = 0;
+            double c = 0;
+            double yc = 0;
+            double r = 0;
+            double t = 0;
+            double y = 0;
+            double z = 0;
+            double pk = 0;
+            double pkm1 = 0;
+            double pkm2 = 0;
+            double qk = 0;
+            double qkm1 = 0;
+            double qkm2 = 0;
+            double tmp = 0;
+
+            igammaepsilon = 0.000000000000001;
+            igammabignumber = 4503599627370496.0;
+            igammabignumberinv = 2.22044604925031308085*0.0000000000000001;
+            if( (double)(x)<=(double)(0) || (double)(a)<=(double)(0) )
+            {
+                result = 1;
+                return result;
+            }
+            if( (double)(x)<(double)(1) || (double)(x)<(double)(a) )
+            {
+                result = 1-incompletegamma(a, x);
+                return result;
+            }
+            ax = a*Math.Log(x)-x-gammafunc.lngamma(a, ref tmp);
+            if( (double)(ax)<(double)(-709.78271289338399) )
+            {
+                result = 0;
+                return result;
+            }
+            ax = Math.Exp(ax);
+            y = 1-a;
+            z = x+y+1;
+            c = 0;
+            pkm2 = 1;
+            qkm2 = x;
+            pkm1 = x+1;
+            qkm1 = z*x;
+            ans = pkm1/qkm1;
+            do
+            {
+                c = c+1;
+                y = y+1;
+                z = z+2;
+                yc = y*c;
+                pk = pkm1*z-pkm2*yc;
+                qk = qkm1*z-qkm2*yc;
+                if( (double)(qk)!=(double)(0) )
+                {
+                    r = pk/qk;
+                    t = Math.Abs((ans-r)/r);
+                    ans = r;
+                }
+                else
+                {
+                    t = 1;
+                }
+                pkm2 = pkm1;
+                pkm1 = pk;
+                qkm2 = qkm1;
+                qkm1 = qk;
+                if( (double)(Math.Abs(pk))>(double)(igammabignumber) )
+                {
+                    pkm2 = pkm2*igammabignumberinv;
+                    pkm1 = pkm1*igammabignumberinv;
+                    qkm2 = qkm2*igammabignumberinv;
+                    qkm1 = qkm1*igammabignumberinv;
+                }
+            }
+            while( (double)(t)>(double)(igammaepsilon) );
+            result = ans*ax;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Inverse of complemented imcomplete gamma integral
+
+        Given p, the function finds x such that
+
+         igamc( a, x ) = p.
+
+        Starting with the approximate value
+
+                3
+         x = a t
+
+         where
+
+         t = 1 - d - ndtri(p) sqrt(d)
+
+        and
+
+         d = 1/9a,
+
+        the routine performs up to 10 Newton iterations to find the
+        root of igamc(a,x) - p = 0.
+
+        ACCURACY:
+
+        Tested at random a, p in the intervals indicated.
+
+                       a        p                      Relative error:
+        arithmetic   domain   domain     # trials      peak         rms
+           IEEE     0.5,100   0,0.5       100000       1.0e-14     1.7e-15
+           IEEE     0.01,0.5  0,0.5       100000       9.0e-14     3.4e-15
+           IEEE    0.5,10000  0,0.5        20000       2.3e-13     3.8e-14
+
+        Cephes Math Library Release 2.8:  June, 2000
+        Copyright 1984, 1987, 1995, 2000 by Stephen L. Moshier
+        *************************************************************************/
+        public static double invincompletegammac(double a,
+            double y0)
+        {
+            double result = 0;
+            double igammaepsilon = 0;
+            double iinvgammabignumber = 0;
+            double x0 = 0;
+            double x1 = 0;
+            double x = 0;
+            double yl = 0;
+            double yh = 0;
+            double y = 0;
+            double d = 0;
+            double lgm = 0;
+            double dithresh = 0;
+            int i = 0;
+            int dir = 0;
+            double tmp = 0;
+
+            igammaepsilon = 0.000000000000001;
+            iinvgammabignumber = 4503599627370496.0;
+            x0 = iinvgammabignumber;
+            yl = 0;
+            x1 = 0;
+            yh = 1;
+            dithresh = 5*igammaepsilon;
+            d = 1/(9*a);
+            y = 1-d-normaldistr.invnormaldistribution(y0)*Math.Sqrt(d);
+            x = a*y*y*y;
+            lgm = gammafunc.lngamma(a, ref tmp);
+            i = 0;
+            while( i<10 )
+            {
+                if( (double)(x)>(double)(x0) || (double)(x)<(double)(x1) )
+                {
+                    d = 0.0625;
+                    break;
+                }
+                y = incompletegammac(a, x);
+                if( (double)(y)<(double)(yl) || (double)(y)>(double)(yh) )
+                {
+                    d = 0.0625;
+                    break;
+                }
+                if( (double)(y)<(double)(y0) )
+                {
+                    x0 = x;
+                    yl = y;
+                }
+                else
+                {
+                    x1 = x;
+                    yh = y;
+                }
+                d = (a-1)*Math.Log(x)-x-lgm;
+                if( (double)(d)<(double)(-709.78271289338399) )
+                {
+                    d = 0.0625;
+                    break;
+                }
+                d = -Math.Exp(d);
+                d = (y-y0)/d;
+                if( (double)(Math.Abs(d/x))<(double)(igammaepsilon) )
+                {
+                    result = x;
+                    return result;
+                }
+                x = x-d;
+                i = i+1;
+            }
+            if( (double)(x0)==(double)(iinvgammabignumber) )
+            {
+                if( (double)(x)<=(double)(0) )
+                {
+                    x = 1;
+                }
+                while( (double)(x0)==(double)(iinvgammabignumber) )
+                {
+                    x = (1+d)*x;
+                    y = incompletegammac(a, x);
+                    if( (double)(y)<(double)(y0) )
+                    {
+                        x0 = x;
+                        yl = y;
+                        break;
+                    }
+                    d = d+d;
+                }
+            }
+            d = 0.5;
+            dir = 0;
+            i = 0;
+            while( i<400 )
+            {
+                x = x1+d*(x0-x1);
+                y = incompletegammac(a, x);
+                lgm = (x0-x1)/(x1+x0);
+                if( (double)(Math.Abs(lgm))<(double)(dithresh) )
+                {
+                    break;
+                }
+                lgm = (y-y0)/y0;
+                if( (double)(Math.Abs(lgm))<(double)(dithresh) )
+                {
+                    break;
+                }
+                if( (double)(x)<=(double)(0.0) )
+                {
+                    break;
+                }
+                if( (double)(y)>=(double)(y0) )
+                {
+                    x1 = x;
+                    yh = y;
+                    if( dir<0 )
+                    {
+                        dir = 0;
+                        d = 0.5;
+                    }
+                    else
+                    {
+                        if( dir>1 )
+                        {
+                            d = 0.5*d+0.5;
+                        }
+                        else
+                        {
+                            d = (y0-yl)/(yh-yl);
+                        }
+                    }
+                    dir = dir+1;
+                }
+                else
+                {
+                    x0 = x;
+                    yl = y;
+                    if( dir>0 )
+                    {
+                        dir = 0;
+                        d = 0.5;
+                    }
+                    else
+                    {
+                        if( dir<-1 )
+                        {
+                            d = 0.5*d;
+                        }
+                        else
+                        {
+                            d = (y0-yl)/(yh-yl);
+                        }
+                    }
+                    dir = dir-1;
+                }
+                i = i+1;
+            }
+            result = x;
+            return result;
+        }
+
+
+    }
+    public class airyf
+    {
+        /*************************************************************************
+        Airy function
+
+        Solution of the differential equation
+
+        y"(x) = xy.
+
+        The function returns the two independent solutions Ai, Bi
+        and their first derivatives Ai'(x), Bi'(x).
+
+        Evaluation is by power series summation for small x,
+        by rational minimax approximations for large x.
+
+
+
+        ACCURACY:
+        Error criterion is absolute when function <= 1, relative
+        when function > 1, except * denotes relative error criterion.
+        For large negative x, the absolute error increases as x^1.5.
+        For large positive x, the relative error increases as x^1.5.
+
+        Arithmetic  domain   function  # trials      peak         rms
+        IEEE        -10, 0     Ai        10000       1.6e-15     2.7e-16
+        IEEE          0, 10    Ai        10000       2.3e-14*    1.8e-15*
+        IEEE        -10, 0     Ai'       10000       4.6e-15     7.6e-16
+        IEEE          0, 10    Ai'       10000       1.8e-14*    1.5e-15*
+        IEEE        -10, 10    Bi        30000       4.2e-15     5.3e-16
+        IEEE        -10, 10    Bi'       30000       4.9e-15     7.3e-16
+
+        Cephes Math Library Release 2.8:  June, 2000
+        Copyright 1984, 1987, 1989, 2000 by Stephen L. Moshier
+        *************************************************************************/
+        public static void airy(double x,
+            ref double ai,
+            ref double aip,
+            ref double bi,
+            ref double bip)
+        {
+            double z = 0;
+            double zz = 0;
+            double t = 0;
+            double f = 0;
+            double g = 0;
+            double uf = 0;
+            double ug = 0;
+            double k = 0;
+            double zeta = 0;
+            double theta = 0;
+            int domflg = 0;
+            double c1 = 0;
+            double c2 = 0;
+            double sqrt3 = 0;
+            double sqpii = 0;
+            double afn = 0;
+            double afd = 0;
+            double agn = 0;
+            double agd = 0;
+            double apfn = 0;
+            double apfd = 0;
+            double apgn = 0;
+            double apgd = 0;
+            double an = 0;
+            double ad = 0;
+            double apn = 0;
+            double apd = 0;
+            double bn16 = 0;
+            double bd16 = 0;
+            double bppn = 0;
+            double bppd = 0;
+
+            ai = 0;
+            aip = 0;
+            bi = 0;
+            bip = 0;
+
+            sqpii = 5.64189583547756286948E-1;
+            c1 = 0.35502805388781723926;
+            c2 = 0.258819403792806798405;
+            sqrt3 = 1.732050807568877293527;
+            domflg = 0;
+            if( (double)(x)>(double)(25.77) )
+            {
+                ai = 0;
+                aip = 0;
+                bi = math.maxrealnumber;
+                bip = math.maxrealnumber;
+                return;
+            }
+            if( (double)(x)<(double)(-2.09) )
+            {
+                domflg = 15;
+                t = Math.Sqrt(-x);
+                zeta = -(2.0*x*t/3.0);
+                t = Math.Sqrt(t);
+                k = sqpii/t;
+                z = 1.0/zeta;
+                zz = z*z;
+                afn = -1.31696323418331795333E-1;
+                afn = afn*zz-6.26456544431912369773E-1;
+                afn = afn*zz-6.93158036036933542233E-1;
+                afn = afn*zz-2.79779981545119124951E-1;
+                afn = afn*zz-4.91900132609500318020E-2;
+                afn = afn*zz-4.06265923594885404393E-3;
+                afn = afn*zz-1.59276496239262096340E-4;
+                afn = afn*zz-2.77649108155232920844E-6;
+                afn = afn*zz-1.67787698489114633780E-8;
+                afd = 1.00000000000000000000E0;
+                afd = afd*zz+1.33560420706553243746E1;
+                afd = afd*zz+3.26825032795224613948E1;
+                afd = afd*zz+2.67367040941499554804E1;
+                afd = afd*zz+9.18707402907259625840E0;
+                afd = afd*zz+1.47529146771666414581E0;
+                afd = afd*zz+1.15687173795188044134E-1;
+                afd = afd*zz+4.40291641615211203805E-3;
+                afd = afd*zz+7.54720348287414296618E-5;
+                afd = afd*zz+4.51850092970580378464E-7;
+                uf = 1.0+zz*afn/afd;
+                agn = 1.97339932091685679179E-2;
+                agn = agn*zz+3.91103029615688277255E-1;
+                agn = agn*zz+1.06579897599595591108E0;
+                agn = agn*zz+9.39169229816650230044E-1;
+                agn = agn*zz+3.51465656105547619242E-1;
+                agn = agn*zz+6.33888919628925490927E-2;
+                agn = agn*zz+5.85804113048388458567E-3;
+                agn = agn*zz+2.82851600836737019778E-4;
+                agn = agn*zz+6.98793669997260967291E-6;
+                agn = agn*zz+8.11789239554389293311E-8;
+                agn = agn*zz+3.41551784765923618484E-10;
+                agd = 1.00000000000000000000E0;
+                agd = agd*zz+9.30892908077441974853E0;
+                agd = agd*zz+1.98352928718312140417E1;
+                agd = agd*zz+1.55646628932864612953E1;
+                agd = agd*zz+5.47686069422975497931E0;
+                agd = agd*zz+9.54293611618961883998E-1;
+                agd = agd*zz+8.64580826352392193095E-2;
+                agd = agd*zz+4.12656523824222607191E-3;
+                agd = agd*zz+1.01259085116509135510E-4;
+                agd = agd*zz+1.17166733214413521882E-6;
+                agd = agd*zz+4.91834570062930015649E-9;
+                ug = z*agn/agd;
+                theta = zeta+0.25*Math.PI;
+                f = Math.Sin(theta);
+                g = Math.Cos(theta);
+                ai = k*(f*uf-g*ug);
+                bi = k*(g*uf+f*ug);
+                apfn = 1.85365624022535566142E-1;
+                apfn = apfn*zz+8.86712188052584095637E-1;
+                apfn = apfn*zz+9.87391981747398547272E-1;
+                apfn = apfn*zz+4.01241082318003734092E-1;
+                apfn = apfn*zz+7.10304926289631174579E-2;
+                apfn = apfn*zz+5.90618657995661810071E-3;
+                apfn = apfn*zz+2.33051409401776799569E-4;
+                apfn = apfn*zz+4.08718778289035454598E-6;
+                apfn = apfn*zz+2.48379932900442457853E-8;
+                apfd = 1.00000000000000000000E0;
+                apfd = apfd*zz+1.47345854687502542552E1;
+                apfd = apfd*zz+3.75423933435489594466E1;
+                apfd = apfd*zz+3.14657751203046424330E1;
+                apfd = apfd*zz+1.09969125207298778536E1;
+                apfd = apfd*zz+1.78885054766999417817E0;
+                apfd = apfd*zz+1.41733275753662636873E-1;
+                apfd = apfd*zz+5.44066067017226003627E-3;
+                apfd = apfd*zz+9.39421290654511171663E-5;
+                apfd = apfd*zz+5.65978713036027009243E-7;
+                uf = 1.0+zz*apfn/apfd;
+                apgn = -3.55615429033082288335E-2;
+                apgn = apgn*zz-6.37311518129435504426E-1;
+                apgn = apgn*zz-1.70856738884312371053E0;
+                apgn = apgn*zz-1.50221872117316635393E0;
+                apgn = apgn*zz-5.63606665822102676611E-1;
+                apgn = apgn*zz-1.02101031120216891789E-1;
+                apgn = apgn*zz-9.48396695961445269093E-3;
+                apgn = apgn*zz-4.60325307486780994357E-4;
+                apgn = apgn*zz-1.14300836484517375919E-5;
+                apgn = apgn*zz-1.33415518685547420648E-7;
+                apgn = apgn*zz-5.63803833958893494476E-10;
+                apgd = 1.00000000000000000000E0;
+                apgd = apgd*zz+9.85865801696130355144E0;
+                apgd = apgd*zz+2.16401867356585941885E1;
+                apgd = apgd*zz+1.73130776389749389525E1;
+                apgd = apgd*zz+6.17872175280828766327E0;
+                apgd = apgd*zz+1.08848694396321495475E0;
+                apgd = apgd*zz+9.95005543440888479402E-2;
+                apgd = apgd*zz+4.78468199683886610842E-3;
+                apgd = apgd*zz+1.18159633322838625562E-4;
+                apgd = apgd*zz+1.37480673554219441465E-6;
+                apgd = apgd*zz+5.79912514929147598821E-9;
+                ug = z*apgn/apgd;
+                k = sqpii*t;
+                aip = -(k*(g*uf+f*ug));
+                bip = k*(f*uf-g*ug);
+                return;
+            }
+            if( (double)(x)>=(double)(2.09) )
+            {
+                domflg = 5;
+                t = Math.Sqrt(x);
+                zeta = 2.0*x*t/3.0;
+                g = Math.Exp(zeta);
+                t = Math.Sqrt(t);
+                k = 2.0*t*g;
+                z = 1.0/zeta;
+                an = 3.46538101525629032477E-1;
+                an = an*z+1.20075952739645805542E1;
+                an = an*z+7.62796053615234516538E1;
+                an = an*z+1.68089224934630576269E2;
+                an = an*z+1.59756391350164413639E2;
+                an = an*z+7.05360906840444183113E1;
+                an = an*z+1.40264691163389668864E1;
+                an = an*z+9.99999999999999995305E-1;
+                ad = 5.67594532638770212846E-1;
+                ad = ad*z+1.47562562584847203173E1;
+                ad = ad*z+8.45138970141474626562E1;
+                ad = ad*z+1.77318088145400459522E2;
+                ad = ad*z+1.64234692871529701831E2;
+                ad = ad*z+7.14778400825575695274E1;
+                ad = ad*z+1.40959135607834029598E1;
+                ad = ad*z+1.00000000000000000470E0;
+                f = an/ad;
+                ai = sqpii*f/k;
+                k = -(0.5*sqpii*t/g);
+                apn = 6.13759184814035759225E-1;
+                apn = apn*z+1.47454670787755323881E1;
+                apn = apn*z+8.20584123476060982430E1;
+                apn = apn*z+1.71184781360976385540E2;
+                apn = apn*z+1.59317847137141783523E2;
+                apn = apn*z+6.99778599330103016170E1;
+                apn = apn*z+1.39470856980481566958E1;
+                apn = apn*z+1.00000000000000000550E0;
+                apd = 3.34203677749736953049E-1;
+                apd = apd*z+1.11810297306158156705E1;
+                apd = apd*z+7.11727352147859965283E1;
+                apd = apd*z+1.58778084372838313640E2;
+                apd = apd*z+1.53206427475809220834E2;
+                apd = apd*z+6.86752304592780337944E1;
+                apd = apd*z+1.38498634758259442477E1;
+                apd = apd*z+9.99999999999999994502E-1;
+                f = apn/apd;
+                aip = f*k;
+                if( (double)(x)>(double)(8.3203353) )
+                {
+                    bn16 = -2.53240795869364152689E-1;
+                    bn16 = bn16*z+5.75285167332467384228E-1;
+                    bn16 = bn16*z-3.29907036873225371650E-1;
+                    bn16 = bn16*z+6.44404068948199951727E-2;
+                    bn16 = bn16*z-3.82519546641336734394E-3;
+                    bd16 = 1.00000000000000000000E0;
+                    bd16 = bd16*z-7.15685095054035237902E0;
+                    bd16 = bd16*z+1.06039580715664694291E1;
+                    bd16 = bd16*z-5.23246636471251500874E0;
+                    bd16 = bd16*z+9.57395864378383833152E-1;
+                    bd16 = bd16*z-5.50828147163549611107E-2;
+                    f = z*bn16/bd16;
+                    k = sqpii*g;
+                    bi = k*(1.0+f)/t;
+                    bppn = 4.65461162774651610328E-1;
+                    bppn = bppn*z-1.08992173800493920734E0;
+                    bppn = bppn*z+6.38800117371827987759E-1;
+                    bppn = bppn*z-1.26844349553102907034E-1;
+                    bppn = bppn*z+7.62487844342109852105E-3;
+                    bppd = 1.00000000000000000000E0;
+                    bppd = bppd*z-8.70622787633159124240E0;
+                    bppd = bppd*z+1.38993162704553213172E1;
+                    bppd = bppd*z-7.14116144616431159572E0;
+                    bppd = bppd*z+1.34008595960680518666E0;
+                    bppd = bppd*z-7.84273211323341930448E-2;
+                    f = z*bppn/bppd;
+                    bip = k*t*(1.0+f);
+                    return;
+                }
+            }
+            f = 1.0;
+            g = x;
+            t = 1.0;
+            uf = 1.0;
+            ug = x;
+            k = 1.0;
+            z = x*x*x;
+            while( (double)(t)>(double)(math.machineepsilon) )
+            {
+                uf = uf*z;
+                k = k+1.0;
+                uf = uf/k;
+                ug = ug*z;
+                k = k+1.0;
+                ug = ug/k;
+                uf = uf/k;
+                f = f+uf;
+                k = k+1.0;
+                ug = ug/k;
+                g = g+ug;
+                t = Math.Abs(uf/f);
+            }
+            uf = c1*f;
+            ug = c2*g;
+            if( domflg%2==0 )
+            {
+                ai = uf-ug;
+            }
+            if( domflg/2%2==0 )
+            {
+                bi = sqrt3*(uf+ug);
+            }
+            k = 4.0;
+            uf = x*x/2.0;
+            ug = z/3.0;
+            f = uf;
+            g = 1.0+ug;
+            uf = uf/3.0;
+            t = 1.0;
+            while( (double)(t)>(double)(math.machineepsilon) )
+            {
+                uf = uf*z;
+                ug = ug/k;
+                k = k+1.0;
+                ug = ug*z;
+                uf = uf/k;
+                f = f+uf;
+                k = k+1.0;
+                ug = ug/k;
+                uf = uf/k;
+                g = g+ug;
+                k = k+1.0;
+                t = Math.Abs(ug/g);
+            }
+            uf = c1*f;
+            ug = c2*g;
+            if( domflg/4%2==0 )
+            {
+                aip = uf-ug;
+            }
+            if( domflg/8%2==0 )
+            {
+                bip = sqrt3*(uf+ug);
+            }
+        }
+
+
+    }
+    public class bessel
+    {
+        /*************************************************************************
+        Bessel function of order zero
+
+        Returns Bessel function of order zero of the argument.
+
+        The domain is divided into the intervals [0, 5] and
+        (5, infinity). In the first interval the following rational
+        approximation is used:
+
+
+               2         2
+        (w - r  ) (w - r  ) P (w) / Q (w)
+              1         2    3       8
+
+                   2
+        where w = x  and the two r's are zeros of the function.
+
+        In the second interval, the Hankel asymptotic expansion
+        is employed with two rational functions of degree 6/6
+        and 7/7.
+
+        ACCURACY:
+
+                             Absolute error:
+        arithmetic   domain     # trials      peak         rms
+           IEEE      0, 30       60000       4.2e-16     1.1e-16
+
+        Cephes Math Library Release 2.8:  June, 2000
+        Copyright 1984, 1987, 1989, 2000 by Stephen L. Moshier
+        *************************************************************************/
+        public static double besselj0(double x)
+        {
+            double result = 0;
+            double xsq = 0;
+            double nn = 0;
+            double pzero = 0;
+            double qzero = 0;
+            double p1 = 0;
+            double q1 = 0;
+
+            if( (double)(x)<(double)(0) )
+            {
+                x = -x;
+            }
+            if( (double)(x)>(double)(8.0) )
+            {
+                besselasympt0(x, ref pzero, ref qzero);
+                nn = x-Math.PI/4;
+                result = Math.Sqrt(2/Math.PI/x)*(pzero*Math.Cos(nn)-qzero*Math.Sin(nn));
+                return result;
+            }
+            xsq = math.sqr(x);
+            p1 = 26857.86856980014981415848441;
+            p1 = -40504123.71833132706360663322+xsq*p1;
+            p1 = 25071582855.36881945555156435+xsq*p1;
+            p1 = -8085222034853.793871199468171+xsq*p1;
+            p1 = 1434354939140344.111664316553+xsq*p1;
+            p1 = -136762035308817138.6865416609+xsq*p1;
+            p1 = 6382059341072356562.289432465+xsq*p1;
+            p1 = -117915762910761053603.8440800+xsq*p1;
+            p1 = 493378725179413356181.6813446+xsq*p1;
+            q1 = 1.0;
+            q1 = 1363.063652328970604442810507+xsq*q1;
+            q1 = 1114636.098462985378182402543+xsq*q1;
+            q1 = 669998767.2982239671814028660+xsq*q1;
+            q1 = 312304311494.1213172572469442+xsq*q1;
+            q1 = 112775673967979.8507056031594+xsq*q1;
+            q1 = 30246356167094626.98627330784+xsq*q1;
+            q1 = 5428918384092285160.200195092+xsq*q1;
+            q1 = 493378725179413356211.3278438+xsq*q1;
+            result = p1/q1;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Bessel function of order one
+
+        Returns Bessel function of order one of the argument.
+
+        The domain is divided into the intervals [0, 8] and
+        (8, infinity). In the first interval a 24 term Chebyshev
+        expansion is used. In the second, the asymptotic
+        trigonometric representation is employed using two
+        rational functions of degree 5/5.
+
+        ACCURACY:
+
+                             Absolute error:
+        arithmetic   domain      # trials      peak         rms
+           IEEE      0, 30       30000       2.6e-16     1.1e-16
+
+        Cephes Math Library Release 2.8:  June, 2000
+        Copyright 1984, 1987, 1989, 2000 by Stephen L. Moshier
+        *************************************************************************/
+        public static double besselj1(double x)
+        {
+            double result = 0;
+            double s = 0;
+            double xsq = 0;
+            double nn = 0;
+            double pzero = 0;
+            double qzero = 0;
+            double p1 = 0;
+            double q1 = 0;
+
+            s = Math.Sign(x);
+            if( (double)(x)<(double)(0) )
+            {
+                x = -x;
+            }
+            if( (double)(x)>(double)(8.0) )
+            {
+                besselasympt1(x, ref pzero, ref qzero);
+                nn = x-3*Math.PI/4;
+                result = Math.Sqrt(2/Math.PI/x)*(pzero*Math.Cos(nn)-qzero*Math.Sin(nn));
+                if( (double)(s)<(double)(0) )
+                {
+                    result = -result;
+                }
+                return result;
+            }
+            xsq = math.sqr(x);
+            p1 = 2701.122710892323414856790990;
+            p1 = -4695753.530642995859767162166+xsq*p1;
+            p1 = 3413234182.301700539091292655+xsq*p1;
+            p1 = -1322983480332.126453125473247+xsq*p1;
+            p1 = 290879526383477.5409737601689+xsq*p1;
+            p1 = -35888175699101060.50743641413+xsq*p1;
+            p1 = 2316433580634002297.931815435+xsq*p1;
+            p1 = -66721065689249162980.20941484+xsq*p1;
+            p1 = 581199354001606143928.050809+xsq*p1;
+            q1 = 1.0;
+            q1 = 1606.931573481487801970916749+xsq*q1;
+            q1 = 1501793.594998585505921097578+xsq*q1;
+            q1 = 1013863514.358673989967045588+xsq*q1;
+            q1 = 524371026216.7649715406728642+xsq*q1;
+            q1 = 208166122130760.7351240184229+xsq*q1;
+            q1 = 60920613989175217.46105196863+xsq*q1;
+            q1 = 11857707121903209998.37113348+xsq*q1;
+            q1 = 1162398708003212287858.529400+xsq*q1;
+            result = s*x*p1/q1;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Bessel function of integer order
+
+        Returns Bessel function of order n, where n is a
+        (possibly negative) integer.
+
+        The ratio of jn(x) to j0(x) is computed by backward
+        recurrence.  First the ratio jn/jn-1 is found by a
+        continued fraction expansion.  Then the recurrence
+        relating successive orders is applied until j0 or j1 is
+        reached.
+
+        If n = 0 or 1 the routine for j0 or j1 is called
+        directly.
+
+        ACCURACY:
+
+                             Absolute error:
+        arithmetic   range      # trials      peak         rms
+           IEEE      0, 30        5000       4.4e-16     7.9e-17
+
+
+        Not suitable for large n or x. Use jv() (fractional order) instead.
+
+        Cephes Math Library Release 2.8:  June, 2000
+        Copyright 1984, 1987, 2000 by Stephen L. Moshier
+        *************************************************************************/
+        public static double besseljn(int n,
+            double x)
+        {
+            double result = 0;
+            double pkm2 = 0;
+            double pkm1 = 0;
+            double pk = 0;
+            double xk = 0;
+            double r = 0;
+            double ans = 0;
+            int k = 0;
+            int sg = 0;
+
+            if( n<0 )
+            {
+                n = -n;
+                if( n%2==0 )
+                {
+                    sg = 1;
+                }
+                else
+                {
+                    sg = -1;
+                }
+            }
+            else
+            {
+                sg = 1;
+            }
+            if( (double)(x)<(double)(0) )
+            {
+                if( n%2!=0 )
+                {
+                    sg = -sg;
+                }
+                x = -x;
+            }
+            if( n==0 )
+            {
+                result = sg*besselj0(x);
+                return result;
+            }
+            if( n==1 )
+            {
+                result = sg*besselj1(x);
+                return result;
+            }
+            if( n==2 )
+            {
+                if( (double)(x)==(double)(0) )
+                {
+                    result = 0;
+                }
+                else
+                {
+                    result = sg*(2.0*besselj1(x)/x-besselj0(x));
+                }
+                return result;
+            }
+            if( (double)(x)<(double)(math.machineepsilon) )
+            {
+                result = 0;
+                return result;
+            }
+            k = 53;
+            pk = 2*(n+k);
+            ans = pk;
+            xk = x*x;
+            do
+            {
+                pk = pk-2.0;
+                ans = pk-xk/ans;
+                k = k-1;
+            }
+            while( k!=0 );
+            ans = x/ans;
+            pk = 1.0;
+            pkm1 = 1.0/ans;
+            k = n-1;
+            r = 2*k;
+            do
+            {
+                pkm2 = (pkm1*r-pk*x)/x;
+                pk = pkm1;
+                pkm1 = pkm2;
+                r = r-2.0;
+                k = k-1;
+            }
+            while( k!=0 );
+            if( (double)(Math.Abs(pk))>(double)(Math.Abs(pkm1)) )
+            {
+                ans = besselj1(x)/pk;
+            }
+            else
+            {
+                ans = besselj0(x)/pkm1;
+            }
+            result = sg*ans;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Bessel function of the second kind, order zero
+
+        Returns Bessel function of the second kind, of order
+        zero, of the argument.
+
+        The domain is divided into the intervals [0, 5] and
+        (5, infinity). In the first interval a rational approximation
+        R(x) is employed to compute
+          y0(x)  = R(x)  +   2 * log(x) * j0(x) / PI.
+        Thus a call to j0() is required.
+
+        In the second interval, the Hankel asymptotic expansion
+        is employed with two rational functions of degree 6/6
+        and 7/7.
+
+
+
+        ACCURACY:
+
+         Absolute error, when y0(x) < 1; else relative error:
+
+        arithmetic   domain     # trials      peak         rms
+           IEEE      0, 30       30000       1.3e-15     1.6e-16
+
+        Cephes Math Library Release 2.8:  June, 2000
+        Copyright 1984, 1987, 1989, 2000 by Stephen L. Moshier
+        *************************************************************************/
+        public static double bessely0(double x)
+        {
+            double result = 0;
+            double nn = 0;
+            double xsq = 0;
+            double pzero = 0;
+            double qzero = 0;
+            double p4 = 0;
+            double q4 = 0;
+
+            if( (double)(x)>(double)(8.0) )
+            {
+                besselasympt0(x, ref pzero, ref qzero);
+                nn = x-Math.PI/4;
+                result = Math.Sqrt(2/Math.PI/x)*(pzero*Math.Sin(nn)+qzero*Math.Cos(nn));
+                return result;
+            }
+            xsq = math.sqr(x);
+            p4 = -41370.35497933148554125235152;
+            p4 = 59152134.65686889654273830069+xsq*p4;
+            p4 = -34363712229.79040378171030138+xsq*p4;
+            p4 = 10255208596863.94284509167421+xsq*p4;
+            p4 = -1648605817185729.473122082537+xsq*p4;
+            p4 = 137562431639934407.8571335453+xsq*p4;
+            p4 = -5247065581112764941.297350814+xsq*p4;
+            p4 = 65874732757195549259.99402049+xsq*p4;
+            p4 = -27502866786291095837.01933175+xsq*p4;
+            q4 = 1.0;
+            q4 = 1282.452772478993804176329391+xsq*q4;
+            q4 = 1001702.641288906265666651753+xsq*q4;
+            q4 = 579512264.0700729537480087915+xsq*q4;
+            q4 = 261306575504.1081249568482092+xsq*q4;
+            q4 = 91620380340751.85262489147968+xsq*q4;
+            q4 = 23928830434997818.57439356652+xsq*q4;
+            q4 = 4192417043410839973.904769661+xsq*q4;
+            q4 = 372645883898616588198.9980+xsq*q4;
+            result = p4/q4+2/Math.PI*besselj0(x)*Math.Log(x);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Bessel function of second kind of order one
+
+        Returns Bessel function of the second kind of order one
+        of the argument.
+
+        The domain is divided into the intervals [0, 8] and
+        (8, infinity). In the first interval a 25 term Chebyshev
+        expansion is used, and a call to j1() is required.
+        In the second, the asymptotic trigonometric representation
+        is employed using two rational functions of degree 5/5.
+
+        ACCURACY:
+
+                             Absolute error:
+        arithmetic   domain      # trials      peak         rms
+           IEEE      0, 30       30000       1.0e-15     1.3e-16
+
+        Cephes Math Library Release 2.8:  June, 2000
+        Copyright 1984, 1987, 1989, 2000 by Stephen L. Moshier
+        *************************************************************************/
+        public static double bessely1(double x)
+        {
+            double result = 0;
+            double nn = 0;
+            double xsq = 0;
+            double pzero = 0;
+            double qzero = 0;
+            double p4 = 0;
+            double q4 = 0;
+
+            if( (double)(x)>(double)(8.0) )
+            {
+                besselasympt1(x, ref pzero, ref qzero);
+                nn = x-3*Math.PI/4;
+                result = Math.Sqrt(2/Math.PI/x)*(pzero*Math.Sin(nn)+qzero*Math.Cos(nn));
+                return result;
+            }
+            xsq = math.sqr(x);
+            p4 = -2108847.540133123652824139923;
+            p4 = 3639488548.124002058278999428+xsq*p4;
+            p4 = -2580681702194.450950541426399+xsq*p4;
+            p4 = 956993023992168.3481121552788+xsq*p4;
+            p4 = -196588746272214065.8820322248+xsq*p4;
+            p4 = 21931073399177975921.11427556+xsq*p4;
+            p4 = -1212297555414509577913.561535+xsq*p4;
+            p4 = 26554738314348543268942.48968+xsq*p4;
+            p4 = -99637534243069222259967.44354+xsq*p4;
+            q4 = 1.0;
+            q4 = 1612.361029677000859332072312+xsq*q4;
+            q4 = 1563282.754899580604737366452+xsq*q4;
+            q4 = 1128686837.169442121732366891+xsq*q4;
+            q4 = 646534088126.5275571961681500+xsq*q4;
+            q4 = 297663212564727.6729292742282+xsq*q4;
+            q4 = 108225825940881955.2553850180+xsq*q4;
+            q4 = 29549879358971486742.90758119+xsq*q4;
+            q4 = 5435310377188854170800.653097+xsq*q4;
+            q4 = 508206736694124324531442.4152+xsq*q4;
+            result = x*p4/q4+2/Math.PI*(besselj1(x)*Math.Log(x)-1/x);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Bessel function of second kind of integer order
+
+        Returns Bessel function of order n, where n is a
+        (possibly negative) integer.
+
+        The function is evaluated by forward recurrence on
+        n, starting with values computed by the routines
+        y0() and y1().
+
+        If n = 0 or 1 the routine for y0 or y1 is called
+        directly.
+
+        ACCURACY:
+                             Absolute error, except relative
+                             when y > 1:
+        arithmetic   domain     # trials      peak         rms
+           IEEE      0, 30       30000       3.4e-15     4.3e-16
+
+        Cephes Math Library Release 2.8:  June, 2000
+        Copyright 1984, 1987, 2000 by Stephen L. Moshier
+        *************************************************************************/
+        public static double besselyn(int n,
+            double x)
+        {
+            double result = 0;
+            int i = 0;
+            double a = 0;
+            double b = 0;
+            double tmp = 0;
+            double s = 0;
+
+            s = 1;
+            if( n<0 )
+            {
+                n = -n;
+                if( n%2!=0 )
+                {
+                    s = -1;
+                }
+            }
+            if( n==0 )
+            {
+                result = bessely0(x);
+                return result;
+            }
+            if( n==1 )
+            {
+                result = s*bessely1(x);
+                return result;
+            }
+            a = bessely0(x);
+            b = bessely1(x);
+            for(i=1; i<=n-1; i++)
+            {
+                tmp = b;
+                b = 2*i/x*b-a;
+                a = tmp;
+            }
+            result = s*b;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Modified Bessel function of order zero
+
+        Returns modified Bessel function of order zero of the
+        argument.
+
+        The function is defined as i0(x) = j0( ix ).
+
+        The range is partitioned into the two intervals [0,8] and
+        (8, infinity).  Chebyshev polynomial expansions are employed
+        in each interval.
+
+        ACCURACY:
+
+                             Relative error:
+        arithmetic   domain     # trials      peak         rms
+           IEEE      0,30        30000       5.8e-16     1.4e-16
+
+        Cephes Math Library Release 2.8:  June, 2000
+        Copyright 1984, 1987, 2000 by Stephen L. Moshier
+        *************************************************************************/
+        public static double besseli0(double x)
+        {
+            double result = 0;
+            double y = 0;
+            double v = 0;
+            double z = 0;
+            double b0 = 0;
+            double b1 = 0;
+            double b2 = 0;
+
+            if( (double)(x)<(double)(0) )
+            {
+                x = -x;
+            }
+            if( (double)(x)<=(double)(8.0) )
+            {
+                y = x/2.0-2.0;
+                besselmfirstcheb(-4.41534164647933937950E-18, ref b0, ref b1, ref b2);
+                besselmnextcheb(y, 3.33079451882223809783E-17, ref b0, ref b1, ref b2);
+                besselmnextcheb(y, -2.43127984654795469359E-16, ref b0, ref b1, ref b2);
+                besselmnextcheb(y, 1.71539128555513303061E-15, ref b0, ref b1, ref b2);
+                besselmnextcheb(y, -1.16853328779934516808E-14, ref b0, ref b1, ref b2);
+                besselmnextcheb(y, 7.67618549860493561688E-14, ref b0, ref b1, ref b2);
+                besselmnextcheb(y, -4.85644678311192946090E-13, ref b0, ref b1, ref b2);
+                besselmnextcheb(y, 2.95505266312963983461E-12, ref b0, ref b1, ref b2);
+                besselmnextcheb(y, -1.72682629144155570723E-11, ref b0, ref b1, ref b2);
+                besselmnextcheb(y, 9.67580903537323691224E-11, ref b0, ref b1, ref b2);
+                besselmnextcheb(y, -5.18979560163526290666E-10, ref b0, ref b1, ref b2);
+                besselmnextcheb(y, 2.65982372468238665035E-9, ref b0, ref b1, ref b2);
+                besselmnextcheb(y, -1.30002500998624804212E-8, ref b0, ref b1, ref b2);
+                besselmnextcheb(y, 6.04699502254191894932E-8, ref b0, ref b1, ref b2);
+                besselmnextcheb(y, -2.67079385394061173391E-7, ref b0, ref b1, ref b2);
+                besselmnextcheb(y, 1.11738753912010371815E-6, ref b0, ref b1, ref b2);
+                besselmnextcheb(y, -4.41673835845875056359E-6, ref b0, ref b1, ref b2);
+                besselmnextcheb(y, 1.64484480707288970893E-5, ref b0, ref b1, ref b2);
+                besselmnextcheb(y, -5.75419501008210370398E-5, ref b0, ref b1, ref b2);
+                besselmnextcheb(y, 1.88502885095841655729E-4, ref b0, ref b1, ref b2);
+                besselmnextcheb(y, -5.76375574538582365885E-4, ref b0, ref b1, ref b2);
+                besselmnextcheb(y, 1.63947561694133579842E-3, ref b0, ref b1, ref b2);
+                besselmnextcheb(y, -4.32430999505057594430E-3, ref b0, ref b1, ref b2);
+                besselmnextcheb(y, 1.05464603945949983183E-2, ref b0, ref b1, ref b2);
+                besselmnextcheb(y, -2.37374148058994688156E-2, ref b0, ref b1, ref b2);
+                besselmnextcheb(y, 4.93052842396707084878E-2, ref b0, ref b1, ref b2);
+                besselmnextcheb(y, -9.49010970480476444210E-2, ref b0, ref b1, ref b2);
+                besselmnextcheb(y, 1.71620901522208775349E-1, ref b0, ref b1, ref b2);
+                besselmnextcheb(y, -3.04682672343198398683E-1, ref b0, ref b1, ref b2);
+                besselmnextcheb(y, 6.76795274409476084995E-1, ref b0, ref b1, ref b2);
+                v = 0.5*(b0-b2);
+                result = Math.Exp(x)*v;
+                return result;
+            }
+            z = 32.0/x-2.0;
+            besselmfirstcheb(-7.23318048787475395456E-18, ref b0, ref b1, ref b2);
+            besselmnextcheb(z, -4.83050448594418207126E-18, ref b0, ref b1, ref b2);
+            besselmnextcheb(z, 4.46562142029675999901E-17, ref b0, ref b1, ref b2);
+            besselmnextcheb(z, 3.46122286769746109310E-17, ref b0, ref b1, ref b2);
+            besselmnextcheb(z, -2.82762398051658348494E-16, ref b0, ref b1, ref b2);
+            besselmnextcheb(z, -3.42548561967721913462E-16, ref b0, ref b1, ref b2);
+            besselmnextcheb(z, 1.77256013305652638360E-15, ref b0, ref b1, ref b2);
+            besselmnextcheb(z, 3.81168066935262242075E-15, ref b0, ref b1, ref b2);
+            besselmnextcheb(z, -9.55484669882830764870E-15, ref b0, ref b1, ref b2);
+            besselmnextcheb(z, -4.15056934728722208663E-14, ref b0, ref b1, ref b2);
+            besselmnextcheb(z, 1.54008621752140982691E-14, ref b0, ref b1, ref b2);
+            besselmnextcheb(z, 3.85277838274214270114E-13, ref b0, ref b1, ref b2);
+            besselmnextcheb(z, 7.18012445138366623367E-13, ref b0, ref b1, ref b2);
+            besselmnextcheb(z, -1.79417853150680611778E-12, ref b0, ref b1, ref b2);
+            besselmnextcheb(z, -1.32158118404477131188E-11, ref b0, ref b1, ref b2);
+            besselmnextcheb(z, -3.14991652796324136454E-11, ref b0, ref b1, ref b2);
+            besselmnextcheb(z, 1.18891471078464383424E-11, ref b0, ref b1, ref b2);
+            besselmnextcheb(z, 4.94060238822496958910E-10, ref b0, ref b1, ref b2);
+            besselmnextcheb(z, 3.39623202570838634515E-9, ref b0, ref b1, ref b2);
+            besselmnextcheb(z, 2.26666899049817806459E-8, ref b0, ref b1, ref b2);
+            besselmnextcheb(z, 2.04891858946906374183E-7, ref b0, ref b1, ref b2);
+            besselmnextcheb(z, 2.89137052083475648297E-6, ref b0, ref b1, ref b2);
+            besselmnextcheb(z, 6.88975834691682398426E-5, ref b0, ref b1, ref b2);
+            besselmnextcheb(z, 3.36911647825569408990E-3, ref b0, ref b1, ref b2);
+            besselmnextcheb(z, 8.04490411014108831608E-1, ref b0, ref b1, ref b2);
+            v = 0.5*(b0-b2);
+            result = Math.Exp(x)*v/Math.Sqrt(x);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Modified Bessel function of order one
+
+        Returns modified Bessel function of order one of the
+        argument.
+
+        The function is defined as i1(x) = -i j1( ix ).
+
+        The range is partitioned into the two intervals [0,8] and
+        (8, infinity).  Chebyshev polynomial expansions are employed
+        in each interval.
+
+        ACCURACY:
+
+                             Relative error:
+        arithmetic   domain     # trials      peak         rms
+           IEEE      0, 30       30000       1.9e-15     2.1e-16
+
+        Cephes Math Library Release 2.8:  June, 2000
+        Copyright 1985, 1987, 2000 by Stephen L. Moshier
+        *************************************************************************/
+        public static double besseli1(double x)
+        {
+            double result = 0;
+            double y = 0;
+            double z = 0;
+            double v = 0;
+            double b0 = 0;
+            double b1 = 0;
+            double b2 = 0;
+
+            z = Math.Abs(x);
+            if( (double)(z)<=(double)(8.0) )
+            {
+                y = z/2.0-2.0;
+                besselm1firstcheb(2.77791411276104639959E-18, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, -2.11142121435816608115E-17, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, 1.55363195773620046921E-16, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, -1.10559694773538630805E-15, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, 7.60068429473540693410E-15, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, -5.04218550472791168711E-14, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, 3.22379336594557470981E-13, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, -1.98397439776494371520E-12, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, 1.17361862988909016308E-11, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, -6.66348972350202774223E-11, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, 3.62559028155211703701E-10, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, -1.88724975172282928790E-9, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, 9.38153738649577178388E-9, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, -4.44505912879632808065E-8, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, 2.00329475355213526229E-7, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, -8.56872026469545474066E-7, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, 3.47025130813767847674E-6, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, -1.32731636560394358279E-5, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, 4.78156510755005422638E-5, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, -1.61760815825896745588E-4, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, 5.12285956168575772895E-4, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, -1.51357245063125314899E-3, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, 4.15642294431288815669E-3, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, -1.05640848946261981558E-2, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, 2.47264490306265168283E-2, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, -5.29459812080949914269E-2, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, 1.02643658689847095384E-1, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, -1.76416518357834055153E-1, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, 2.52587186443633654823E-1, ref b0, ref b1, ref b2);
+                v = 0.5*(b0-b2);
+                z = v*z*Math.Exp(z);
+            }
+            else
+            {
+                y = 32.0/z-2.0;
+                besselm1firstcheb(7.51729631084210481353E-18, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, 4.41434832307170791151E-18, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, -4.65030536848935832153E-17, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, -3.20952592199342395980E-17, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, 2.96262899764595013876E-16, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, 3.30820231092092828324E-16, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, -1.88035477551078244854E-15, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, -3.81440307243700780478E-15, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, 1.04202769841288027642E-14, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, 4.27244001671195135429E-14, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, -2.10154184277266431302E-14, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, -4.08355111109219731823E-13, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, -7.19855177624590851209E-13, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, 2.03562854414708950722E-12, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, 1.41258074366137813316E-11, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, 3.25260358301548823856E-11, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, -1.89749581235054123450E-11, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, -5.58974346219658380687E-10, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, -3.83538038596423702205E-9, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, -2.63146884688951950684E-8, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, -2.51223623787020892529E-7, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, -3.88256480887769039346E-6, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, -1.10588938762623716291E-4, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, -9.76109749136146840777E-3, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, 7.78576235018280120474E-1, ref b0, ref b1, ref b2);
+                v = 0.5*(b0-b2);
+                z = v*Math.Exp(z)/Math.Sqrt(z);
+            }
+            if( (double)(x)<(double)(0) )
+            {
+                z = -z;
+            }
+            result = z;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Modified Bessel function, second kind, order zero
+
+        Returns modified Bessel function of the second kind
+        of order zero of the argument.
+
+        The range is partitioned into the two intervals [0,8] and
+        (8, infinity).  Chebyshev polynomial expansions are employed
+        in each interval.
+
+        ACCURACY:
+
+        Tested at 2000 random points between 0 and 8.  Peak absolute
+        error (relative when K0 > 1) was 1.46e-14; rms, 4.26e-15.
+                             Relative error:
+        arithmetic   domain     # trials      peak         rms
+           IEEE      0, 30       30000       1.2e-15     1.6e-16
+
+        Cephes Math Library Release 2.8:  June, 2000
+        Copyright 1984, 1987, 2000 by Stephen L. Moshier
+        *************************************************************************/
+        public static double besselk0(double x)
+        {
+            double result = 0;
+            double y = 0;
+            double z = 0;
+            double v = 0;
+            double b0 = 0;
+            double b1 = 0;
+            double b2 = 0;
+
+            alglib.ap.assert((double)(x)>(double)(0), "Domain error in BesselK0: x<=0");
+            if( (double)(x)<=(double)(2) )
+            {
+                y = x*x-2.0;
+                besselmfirstcheb(1.37446543561352307156E-16, ref b0, ref b1, ref b2);
+                besselmnextcheb(y, 4.25981614279661018399E-14, ref b0, ref b1, ref b2);
+                besselmnextcheb(y, 1.03496952576338420167E-11, ref b0, ref b1, ref b2);
+                besselmnextcheb(y, 1.90451637722020886025E-9, ref b0, ref b1, ref b2);
+                besselmnextcheb(y, 2.53479107902614945675E-7, ref b0, ref b1, ref b2);
+                besselmnextcheb(y, 2.28621210311945178607E-5, ref b0, ref b1, ref b2);
+                besselmnextcheb(y, 1.26461541144692592338E-3, ref b0, ref b1, ref b2);
+                besselmnextcheb(y, 3.59799365153615016266E-2, ref b0, ref b1, ref b2);
+                besselmnextcheb(y, 3.44289899924628486886E-1, ref b0, ref b1, ref b2);
+                besselmnextcheb(y, -5.35327393233902768720E-1, ref b0, ref b1, ref b2);
+                v = 0.5*(b0-b2);
+                v = v-Math.Log(0.5*x)*besseli0(x);
+            }
+            else
+            {
+                z = 8.0/x-2.0;
+                besselmfirstcheb(5.30043377268626276149E-18, ref b0, ref b1, ref b2);
+                besselmnextcheb(z, -1.64758043015242134646E-17, ref b0, ref b1, ref b2);
+                besselmnextcheb(z, 5.21039150503902756861E-17, ref b0, ref b1, ref b2);
+                besselmnextcheb(z, -1.67823109680541210385E-16, ref b0, ref b1, ref b2);
+                besselmnextcheb(z, 5.51205597852431940784E-16, ref b0, ref b1, ref b2);
+                besselmnextcheb(z, -1.84859337734377901440E-15, ref b0, ref b1, ref b2);
+                besselmnextcheb(z, 6.34007647740507060557E-15, ref b0, ref b1, ref b2);
+                besselmnextcheb(z, -2.22751332699166985548E-14, ref b0, ref b1, ref b2);
+                besselmnextcheb(z, 8.03289077536357521100E-14, ref b0, ref b1, ref b2);
+                besselmnextcheb(z, -2.98009692317273043925E-13, ref b0, ref b1, ref b2);
+                besselmnextcheb(z, 1.14034058820847496303E-12, ref b0, ref b1, ref b2);
+                besselmnextcheb(z, -4.51459788337394416547E-12, ref b0, ref b1, ref b2);
+                besselmnextcheb(z, 1.85594911495471785253E-11, ref b0, ref b1, ref b2);
+                besselmnextcheb(z, -7.95748924447710747776E-11, ref b0, ref b1, ref b2);
+                besselmnextcheb(z, 3.57739728140030116597E-10, ref b0, ref b1, ref b2);
+                besselmnextcheb(z, -1.69753450938905987466E-9, ref b0, ref b1, ref b2);
+                besselmnextcheb(z, 8.57403401741422608519E-9, ref b0, ref b1, ref b2);
+                besselmnextcheb(z, -4.66048989768794782956E-8, ref b0, ref b1, ref b2);
+                besselmnextcheb(z, 2.76681363944501510342E-7, ref b0, ref b1, ref b2);
+                besselmnextcheb(z, -1.83175552271911948767E-6, ref b0, ref b1, ref b2);
+                besselmnextcheb(z, 1.39498137188764993662E-5, ref b0, ref b1, ref b2);
+                besselmnextcheb(z, -1.28495495816278026384E-4, ref b0, ref b1, ref b2);
+                besselmnextcheb(z, 1.56988388573005337491E-3, ref b0, ref b1, ref b2);
+                besselmnextcheb(z, -3.14481013119645005427E-2, ref b0, ref b1, ref b2);
+                besselmnextcheb(z, 2.44030308206595545468E0, ref b0, ref b1, ref b2);
+                v = 0.5*(b0-b2);
+                v = v*Math.Exp(-x)/Math.Sqrt(x);
+            }
+            result = v;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Modified Bessel function, second kind, order one
+
+        Computes the modified Bessel function of the second kind
+        of order one of the argument.
+
+        The range is partitioned into the two intervals [0,2] and
+        (2, infinity).  Chebyshev polynomial expansions are employed
+        in each interval.
+
+        ACCURACY:
+
+                             Relative error:
+        arithmetic   domain     # trials      peak         rms
+           IEEE      0, 30       30000       1.2e-15     1.6e-16
+
+        Cephes Math Library Release 2.8:  June, 2000
+        Copyright 1984, 1987, 2000 by Stephen L. Moshier
+        *************************************************************************/
+        public static double besselk1(double x)
+        {
+            double result = 0;
+            double y = 0;
+            double z = 0;
+            double v = 0;
+            double b0 = 0;
+            double b1 = 0;
+            double b2 = 0;
+
+            z = 0.5*x;
+            alglib.ap.assert((double)(z)>(double)(0), "Domain error in K1");
+            if( (double)(x)<=(double)(2) )
+            {
+                y = x*x-2.0;
+                besselm1firstcheb(-7.02386347938628759343E-18, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, -2.42744985051936593393E-15, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, -6.66690169419932900609E-13, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, -1.41148839263352776110E-10, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, -2.21338763073472585583E-8, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, -2.43340614156596823496E-6, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, -1.73028895751305206302E-4, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, -6.97572385963986435018E-3, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, -1.22611180822657148235E-1, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, -3.53155960776544875667E-1, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, 1.52530022733894777053E0, ref b0, ref b1, ref b2);
+                v = 0.5*(b0-b2);
+                result = Math.Log(z)*besseli1(x)+v/x;
+            }
+            else
+            {
+                y = 8.0/x-2.0;
+                besselm1firstcheb(-5.75674448366501715755E-18, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, 1.79405087314755922667E-17, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, -5.68946255844285935196E-17, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, 1.83809354436663880070E-16, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, -6.05704724837331885336E-16, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, 2.03870316562433424052E-15, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, -7.01983709041831346144E-15, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, 2.47715442448130437068E-14, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, -8.97670518232499435011E-14, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, 3.34841966607842919884E-13, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, -1.28917396095102890680E-12, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, 5.13963967348173025100E-12, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, -2.12996783842756842877E-11, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, 9.21831518760500529508E-11, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, -4.19035475934189648750E-10, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, 2.01504975519703286596E-9, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, -1.03457624656780970260E-8, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, 5.74108412545004946722E-8, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, -3.50196060308781257119E-7, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, 2.40648494783721712015E-6, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, -1.93619797416608296024E-5, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, 1.95215518471351631108E-4, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, -2.85781685962277938680E-3, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, 1.03923736576817238437E-1, ref b0, ref b1, ref b2);
+                besselm1nextcheb(y, 2.72062619048444266945E0, ref b0, ref b1, ref b2);
+                v = 0.5*(b0-b2);
+                result = Math.Exp(-x)*v/Math.Sqrt(x);
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        Modified Bessel function, second kind, integer order
+
+        Returns modified Bessel function of the second kind
+        of order n of the argument.
+
+        The range is partitioned into the two intervals [0,9.55] and
+        (9.55, infinity).  An ascending power series is used in the
+        low range, and an asymptotic expansion in the high range.
+
+        ACCURACY:
+
+                             Relative error:
+        arithmetic   domain     # trials      peak         rms
+           IEEE      0,30        90000       1.8e-8      3.0e-10
+
+        Error is high only near the crossover point x = 9.55
+        between the two expansions used.
+
+        Cephes Math Library Release 2.8:  June, 2000
+        Copyright 1984, 1987, 1988, 2000 by Stephen L. Moshier
+        *************************************************************************/
+        public static double besselkn(int nn,
+            double x)
+        {
+            double result = 0;
+            double k = 0;
+            double kf = 0;
+            double nk1f = 0;
+            double nkf = 0;
+            double zn = 0;
+            double t = 0;
+            double s = 0;
+            double z0 = 0;
+            double z = 0;
+            double ans = 0;
+            double fn = 0;
+            double pn = 0;
+            double pk = 0;
+            double zmn = 0;
+            double tlg = 0;
+            double tox = 0;
+            int i = 0;
+            int n = 0;
+            double eul = 0;
+
+            eul = 5.772156649015328606065e-1;
+            if( nn<0 )
+            {
+                n = -nn;
+            }
+            else
+            {
+                n = nn;
+            }
+            alglib.ap.assert(n<=31, "Overflow in BesselKN");
+            alglib.ap.assert((double)(x)>(double)(0), "Domain error in BesselKN");
+            if( (double)(x)<=(double)(9.55) )
+            {
+                ans = 0.0;
+                z0 = 0.25*x*x;
+                fn = 1.0;
+                pn = 0.0;
+                zmn = 1.0;
+                tox = 2.0/x;
+                if( n>0 )
+                {
+                    pn = -eul;
+                    k = 1.0;
+                    for(i=1; i<=n-1; i++)
+                    {
+                        pn = pn+1.0/k;
+                        k = k+1.0;
+                        fn = fn*k;
+                    }
+                    zmn = tox;
+                    if( n==1 )
+                    {
+                        ans = 1.0/x;
+                    }
+                    else
+                    {
+                        nk1f = fn/n;
+                        kf = 1.0;
+                        s = nk1f;
+                        z = -z0;
+                        zn = 1.0;
+                        for(i=1; i<=n-1; i++)
+                        {
+                            nk1f = nk1f/(n-i);
+                            kf = kf*i;
+                            zn = zn*z;
+                            t = nk1f*zn/kf;
+                            s = s+t;
+                            alglib.ap.assert((double)(math.maxrealnumber-Math.Abs(t))>(double)(Math.Abs(s)), "Overflow in BesselKN");
+                            alglib.ap.assert(!((double)(tox)>(double)(1.0) && (double)(math.maxrealnumber/tox)<(double)(zmn)), "Overflow in BesselKN");
+                            zmn = zmn*tox;
+                        }
+                        s = s*0.5;
+                        t = Math.Abs(s);
+                        alglib.ap.assert(!((double)(zmn)>(double)(1.0) && (double)(math.maxrealnumber/zmn)<(double)(t)), "Overflow in BesselKN");
+                        alglib.ap.assert(!((double)(t)>(double)(1.0) && (double)(math.maxrealnumber/t)<(double)(zmn)), "Overflow in BesselKN");
+                        ans = s*zmn;
+                    }
+                }
+                tlg = 2.0*Math.Log(0.5*x);
+                pk = -eul;
+                if( n==0 )
+                {
+                    pn = pk;
+                    t = 1.0;
+                }
+                else
+                {
+                    pn = pn+1.0/n;
+                    t = 1.0/fn;
+                }
+                s = (pk+pn-tlg)*t;
+                k = 1.0;
+                do
+                {
+                    t = t*(z0/(k*(k+n)));
+                    pk = pk+1.0/k;
+                    pn = pn+1.0/(k+n);
+                    s = s+(pk+pn-tlg)*t;
+                    k = k+1.0;
+                }
+                while( (double)(Math.Abs(t/s))>(double)(math.machineepsilon) );
+                s = 0.5*s/zmn;
+                if( n%2!=0 )
+                {
+                    s = -s;
+                }
+                ans = ans+s;
+                result = ans;
+                return result;
+            }
+            if( (double)(x)>(double)(Math.Log(math.maxrealnumber)) )
+            {
+                result = 0;
+                return result;
+            }
+            k = n;
+            pn = 4.0*k*k;
+            pk = 1.0;
+            z0 = 8.0*x;
+            fn = 1.0;
+            t = 1.0;
+            s = t;
+            nkf = math.maxrealnumber;
+            i = 0;
+            do
+            {
+                z = pn-pk*pk;
+                t = t*z/(fn*z0);
+                nk1f = Math.Abs(t);
+                if( i>=n && (double)(nk1f)>(double)(nkf) )
+                {
+                    break;
+                }
+                nkf = nk1f;
+                s = s+t;
+                fn = fn+1.0;
+                pk = pk+2.0;
+                i = i+1;
+            }
+            while( (double)(Math.Abs(t/s))>(double)(math.machineepsilon) );
+            result = Math.Exp(-x)*Math.Sqrt(Math.PI/(2.0*x))*s;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Internal subroutine
+
+        Cephes Math Library Release 2.8:  June, 2000
+        Copyright 1984, 1987, 2000 by Stephen L. Moshier
+        *************************************************************************/
+        private static void besselmfirstcheb(double c,
+            ref double b0,
+            ref double b1,
+            ref double b2)
+        {
+            b0 = c;
+            b1 = 0.0;
+            b2 = 0.0;
+        }
+
+
+        /*************************************************************************
+        Internal subroutine
+
+        Cephes Math Library Release 2.8:  June, 2000
+        Copyright 1984, 1987, 2000 by Stephen L. Moshier
+        *************************************************************************/
+        private static void besselmnextcheb(double x,
+            double c,
+            ref double b0,
+            ref double b1,
+            ref double b2)
+        {
+            b2 = b1;
+            b1 = b0;
+            b0 = x*b1-b2+c;
+        }
+
+
+        /*************************************************************************
+        Internal subroutine
+
+        Cephes Math Library Release 2.8:  June, 2000
+        Copyright 1984, 1987, 2000 by Stephen L. Moshier
+        *************************************************************************/
+        private static void besselm1firstcheb(double c,
+            ref double b0,
+            ref double b1,
+            ref double b2)
+        {
+            b0 = c;
+            b1 = 0.0;
+            b2 = 0.0;
+        }
+
+
+        /*************************************************************************
+        Internal subroutine
+
+        Cephes Math Library Release 2.8:  June, 2000
+        Copyright 1984, 1987, 2000 by Stephen L. Moshier
+        *************************************************************************/
+        private static void besselm1nextcheb(double x,
+            double c,
+            ref double b0,
+            ref double b1,
+            ref double b2)
+        {
+            b2 = b1;
+            b1 = b0;
+            b0 = x*b1-b2+c;
+        }
+
+
+        private static void besselasympt0(double x,
+            ref double pzero,
+            ref double qzero)
+        {
+            double xsq = 0;
+            double p2 = 0;
+            double q2 = 0;
+            double p3 = 0;
+            double q3 = 0;
+
+            pzero = 0;
+            qzero = 0;
+
+            xsq = 64.0/(x*x);
+            p2 = 0.0;
+            p2 = 2485.271928957404011288128951+xsq*p2;
+            p2 = 153982.6532623911470917825993+xsq*p2;
+            p2 = 2016135.283049983642487182349+xsq*p2;
+            p2 = 8413041.456550439208464315611+xsq*p2;
+            p2 = 12332384.76817638145232406055+xsq*p2;
+            p2 = 5393485.083869438325262122897+xsq*p2;
+            q2 = 1.0;
+            q2 = 2615.700736920839685159081813+xsq*q2;
+            q2 = 156001.7276940030940592769933+xsq*q2;
+            q2 = 2025066.801570134013891035236+xsq*q2;
+            q2 = 8426449.050629797331554404810+xsq*q2;
+            q2 = 12338310.22786324960844856182+xsq*q2;
+            q2 = 5393485.083869438325560444960+xsq*q2;
+            p3 = -0.0;
+            p3 = -4.887199395841261531199129300+xsq*p3;
+            p3 = -226.2630641933704113967255053+xsq*p3;
+            p3 = -2365.956170779108192723612816+xsq*p3;
+            p3 = -8239.066313485606568803548860+xsq*p3;
+            p3 = -10381.41698748464093880530341+xsq*p3;
+            p3 = -3984.617357595222463506790588+xsq*p3;
+            q3 = 1.0;
+            q3 = 408.7714673983499223402830260+xsq*q3;
+            q3 = 15704.89191515395519392882766+xsq*q3;
+            q3 = 156021.3206679291652539287109+xsq*q3;
+            q3 = 533291.3634216897168722255057+xsq*q3;
+            q3 = 666745.4239319826986004038103+xsq*q3;
+            q3 = 255015.5108860942382983170882+xsq*q3;
+            pzero = p2/q2;
+            qzero = 8*p3/q3/x;
+        }
+
+
+        private static void besselasympt1(double x,
+            ref double pzero,
+            ref double qzero)
+        {
+            double xsq = 0;
+            double p2 = 0;
+            double q2 = 0;
+            double p3 = 0;
+            double q3 = 0;
+
+            pzero = 0;
+            qzero = 0;
+
+            xsq = 64.0/(x*x);
+            p2 = -1611.616644324610116477412898;
+            p2 = -109824.0554345934672737413139+xsq*p2;
+            p2 = -1523529.351181137383255105722+xsq*p2;
+            p2 = -6603373.248364939109255245434+xsq*p2;
+            p2 = -9942246.505077641195658377899+xsq*p2;
+            p2 = -4435757.816794127857114720794+xsq*p2;
+            q2 = 1.0;
+            q2 = -1455.009440190496182453565068+xsq*q2;
+            q2 = -107263.8599110382011903063867+xsq*q2;
+            q2 = -1511809.506634160881644546358+xsq*q2;
+            q2 = -6585339.479723087072826915069+xsq*q2;
+            q2 = -9934124.389934585658967556309+xsq*q2;
+            q2 = -4435757.816794127856828016962+xsq*q2;
+            p3 = 35.26513384663603218592175580;
+            p3 = 1706.375429020768002061283546+xsq*p3;
+            p3 = 18494.26287322386679652009819+xsq*p3;
+            p3 = 66178.83658127083517939992166+xsq*p3;
+            p3 = 85145.16067533570196555001171+xsq*p3;
+            p3 = 33220.91340985722351859704442+xsq*p3;
+            q3 = 1.0;
+            q3 = 863.8367769604990967475517183+xsq*q3;
+            q3 = 37890.22974577220264142952256+xsq*q3;
+            q3 = 400294.4358226697511708610813+xsq*q3;
+            q3 = 1419460.669603720892855755253+xsq*q3;
+            q3 = 1819458.042243997298924553839+xsq*q3;
+            q3 = 708712.8194102874357377502472+xsq*q3;
+            pzero = p2/q2;
+            qzero = 8*p3/q3/x;
+        }
+
+
+    }
+    public class betaf
+    {
+        /*************************************************************************
+        Beta function
+
+
+                          -     -
+                         | (a) | (b)
+        beta( a, b )  =  -----------.
+                            -
+                           | (a+b)
+
+        For large arguments the logarithm of the function is
+        evaluated using lgam(), then exponentiated.
+
+        ACCURACY:
+
+                             Relative error:
+        arithmetic   domain     # trials      peak         rms
+           IEEE       0,30       30000       8.1e-14     1.1e-14
+
+        Cephes Math Library Release 2.0:  April, 1987
+        Copyright 1984, 1987 by Stephen L. Moshier
+        *************************************************************************/
+        public static double beta(double a,
+            double b)
+        {
+            double result = 0;
+            double y = 0;
+            double sg = 0;
+            double s = 0;
+
+            sg = 1;
+            alglib.ap.assert((double)(a)>(double)(0) || (double)(a)!=(double)((int)Math.Floor(a)), "Overflow in Beta");
+            alglib.ap.assert((double)(b)>(double)(0) || (double)(b)!=(double)((int)Math.Floor(b)), "Overflow in Beta");
+            y = a+b;
+            if( (double)(Math.Abs(y))>(double)(171.624376956302725) )
+            {
+                y = gammafunc.lngamma(y, ref s);
+                sg = sg*s;
+                y = gammafunc.lngamma(b, ref s)-y;
+                sg = sg*s;
+                y = gammafunc.lngamma(a, ref s)+y;
+                sg = sg*s;
+                alglib.ap.assert((double)(y)<=(double)(Math.Log(math.maxrealnumber)), "Overflow in Beta");
+                result = sg*Math.Exp(y);
+                return result;
+            }
+            y = gammafunc.gammafunction(y);
+            alglib.ap.assert((double)(y)!=(double)(0), "Overflow in Beta");
+            if( (double)(a)>(double)(b) )
+            {
+                y = gammafunc.gammafunction(a)/y;
+                y = y*gammafunc.gammafunction(b);
+            }
+            else
+            {
+                y = gammafunc.gammafunction(b)/y;
+                y = y*gammafunc.gammafunction(a);
+            }
+            result = y;
+            return result;
+        }
+
+
+    }
+    public class ibetaf
+    {
+        /*************************************************************************
+        Incomplete beta integral
+
+        Returns incomplete beta integral of the arguments, evaluated
+        from zero to x.  The function is defined as
+
+                         x
+            -            -
+           | (a+b)      | |  a-1     b-1
+         -----------    |   t   (1-t)   dt.
+          -     -     | |
+         | (a) | (b)   -
+                        0
+
+        The domain of definition is 0 <= x <= 1.  In this
+        implementation a and b are restricted to positive values.
+        The integral from x to 1 may be obtained by the symmetry
+        relation
+
+           1 - incbet( a, b, x )  =  incbet( b, a, 1-x ).
+
+        The integral is evaluated by a continued fraction expansion
+        or, when b*x is small, by a power series.
+
+        ACCURACY:
+
+        Tested at uniformly distributed random points (a,b,x) with a and b
+        in "domain" and x between 0 and 1.
+                                               Relative error
+        arithmetic   domain     # trials      peak         rms
+           IEEE      0,5         10000       6.9e-15     4.5e-16
+           IEEE      0,85       250000       2.2e-13     1.7e-14
+           IEEE      0,1000      30000       5.3e-12     6.3e-13
+           IEEE      0,10000    250000       9.3e-11     7.1e-12
+           IEEE      0,100000    10000       8.7e-10     4.8e-11
+        Outputs smaller than the IEEE gradual underflow threshold
+        were excluded from these statistics.
+
+        Cephes Math Library, Release 2.8:  June, 2000
+        Copyright 1984, 1995, 2000 by Stephen L. Moshier
+        *************************************************************************/
+        public static double incompletebeta(double a,
+            double b,
+            double x)
+        {
+            double result = 0;
+            double t = 0;
+            double xc = 0;
+            double w = 0;
+            double y = 0;
+            int flag = 0;
+            double sg = 0;
+            double big = 0;
+            double biginv = 0;
+            double maxgam = 0;
+            double minlog = 0;
+            double maxlog = 0;
+
+            big = 4.503599627370496e15;
+            biginv = 2.22044604925031308085e-16;
+            maxgam = 171.624376956302725;
+            minlog = Math.Log(math.minrealnumber);
+            maxlog = Math.Log(math.maxrealnumber);
+            alglib.ap.assert((double)(a)>(double)(0) && (double)(b)>(double)(0), "Domain error in IncompleteBeta");
+            alglib.ap.assert((double)(x)>=(double)(0) && (double)(x)<=(double)(1), "Domain error in IncompleteBeta");
+            if( (double)(x)==(double)(0) )
+            {
+                result = 0;
+                return result;
+            }
+            if( (double)(x)==(double)(1) )
+            {
+                result = 1;
+                return result;
+            }
+            flag = 0;
+            if( (double)(b*x)<=(double)(1.0) && (double)(x)<=(double)(0.95) )
+            {
+                result = incompletebetaps(a, b, x, maxgam);
+                return result;
+            }
+            w = 1.0-x;
+            if( (double)(x)>(double)(a/(a+b)) )
+            {
+                flag = 1;
+                t = a;
+                a = b;
+                b = t;
+                xc = x;
+                x = w;
+            }
+            else
+            {
+                xc = w;
+            }
+            if( (flag==1 && (double)(b*x)<=(double)(1.0)) && (double)(x)<=(double)(0.95) )
+            {
+                t = incompletebetaps(a, b, x, maxgam);
+                if( (double)(t)<=(double)(math.machineepsilon) )
+                {
+                    result = 1.0-math.machineepsilon;
+                }
+                else
+                {
+                    result = 1.0-t;
+                }
+                return result;
+            }
+            y = x*(a+b-2.0)-(a-1.0);
+            if( (double)(y)<(double)(0.0) )
+            {
+                w = incompletebetafe(a, b, x, big, biginv);
+            }
+            else
+            {
+                w = incompletebetafe2(a, b, x, big, biginv)/xc;
+            }
+            y = a*Math.Log(x);
+            t = b*Math.Log(xc);
+            if( ((double)(a+b)<(double)(maxgam) && (double)(Math.Abs(y))<(double)(maxlog)) && (double)(Math.Abs(t))<(double)(maxlog) )
+            {
+                t = Math.Pow(xc, b);
+                t = t*Math.Pow(x, a);
+                t = t/a;
+                t = t*w;
+                t = t*(gammafunc.gammafunction(a+b)/(gammafunc.gammafunction(a)*gammafunc.gammafunction(b)));
+                if( flag==1 )
+                {
+                    if( (double)(t)<=(double)(math.machineepsilon) )
+                    {
+                        result = 1.0-math.machineepsilon;
+                    }
+                    else
+                    {
+                        result = 1.0-t;
+                    }
+                }
+                else
+                {
+                    result = t;
+                }
+                return result;
+            }
+            y = y+t+gammafunc.lngamma(a+b, ref sg)-gammafunc.lngamma(a, ref sg)-gammafunc.lngamma(b, ref sg);
+            y = y+Math.Log(w/a);
+            if( (double)(y)<(double)(minlog) )
+            {
+                t = 0.0;
+            }
+            else
+            {
+                t = Math.Exp(y);
+            }
+            if( flag==1 )
+            {
+                if( (double)(t)<=(double)(math.machineepsilon) )
+                {
+                    t = 1.0-math.machineepsilon;
+                }
+                else
+                {
+                    t = 1.0-t;
+                }
+            }
+            result = t;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Inverse of imcomplete beta integral
+
+        Given y, the function finds x such that
+
+         incbet( a, b, x ) = y .
+
+        The routine performs interval halving or Newton iterations to find the
+        root of incbet(a,b,x) - y = 0.
+
+
+        ACCURACY:
+
+                             Relative error:
+                       x     a,b
+        arithmetic   domain  domain  # trials    peak       rms
+           IEEE      0,1    .5,10000   50000    5.8e-12   1.3e-13
+           IEEE      0,1   .25,100    100000    1.8e-13   3.9e-15
+           IEEE      0,1     0,5       50000    1.1e-12   5.5e-15
+        With a and b constrained to half-integer or integer values:
+           IEEE      0,1    .5,10000   50000    5.8e-12   1.1e-13
+           IEEE      0,1    .5,100    100000    1.7e-14   7.9e-16
+        With a = .5, b constrained to half-integer or integer values:
+           IEEE      0,1    .5,10000   10000    8.3e-11   1.0e-11
+
+        Cephes Math Library Release 2.8:  June, 2000
+        Copyright 1984, 1996, 2000 by Stephen L. Moshier
+        *************************************************************************/
+        public static double invincompletebeta(double a,
+            double b,
+            double y)
+        {
+            double result = 0;
+            double aaa = 0;
+            double bbb = 0;
+            double y0 = 0;
+            double d = 0;
+            double yyy = 0;
+            double x = 0;
+            double x0 = 0;
+            double x1 = 0;
+            double lgm = 0;
+            double yp = 0;
+            double di = 0;
+            double dithresh = 0;
+            double yl = 0;
+            double yh = 0;
+            double xt = 0;
+            int i = 0;
+            int rflg = 0;
+            int dir = 0;
+            int nflg = 0;
+            double s = 0;
+            int mainlooppos = 0;
+            int ihalve = 0;
+            int ihalvecycle = 0;
+            int newt = 0;
+            int newtcycle = 0;
+            int breaknewtcycle = 0;
+            int breakihalvecycle = 0;
+
+            i = 0;
+            alglib.ap.assert((double)(y)>=(double)(0) && (double)(y)<=(double)(1), "Domain error in InvIncompleteBeta");
+            
+            //
+            // special cases
+            //
+            if( (double)(y)==(double)(0) )
+            {
+                result = 0;
+                return result;
+            }
+            if( (double)(y)==(double)(1.0) )
+            {
+                result = 1;
+                return result;
+            }
+            
+            //
+            // these initializations are not really necessary,
+            // but without them compiler complains about 'possibly uninitialized variables'.
+            //
+            dithresh = 0;
+            rflg = 0;
+            aaa = 0;
+            bbb = 0;
+            y0 = 0;
+            x = 0;
+            yyy = 0;
+            lgm = 0;
+            dir = 0;
+            di = 0;
+            
+            //
+            // normal initializations
+            //
+            x0 = 0.0;
+            yl = 0.0;
+            x1 = 1.0;
+            yh = 1.0;
+            nflg = 0;
+            mainlooppos = 0;
+            ihalve = 1;
+            ihalvecycle = 2;
+            newt = 3;
+            newtcycle = 4;
+            breaknewtcycle = 5;
+            breakihalvecycle = 6;
+            
+            //
+            // main loop
+            //
+            while( true )
+            {
+                
+                //
+                // start
+                //
+                if( mainlooppos==0 )
+                {
+                    if( (double)(a)<=(double)(1.0) || (double)(b)<=(double)(1.0) )
+                    {
+                        dithresh = 1.0e-6;
+                        rflg = 0;
+                        aaa = a;
+                        bbb = b;
+                        y0 = y;
+                        x = aaa/(aaa+bbb);
+                        yyy = incompletebeta(aaa, bbb, x);
+                        mainlooppos = ihalve;
+                        continue;
+                    }
+                    else
+                    {
+                        dithresh = 1.0e-4;
+                    }
+                    yp = -normaldistr.invnormaldistribution(y);
+                    if( (double)(y)>(double)(0.5) )
+                    {
+                        rflg = 1;
+                        aaa = b;
+                        bbb = a;
+                        y0 = 1.0-y;
+                        yp = -yp;
+                    }
+                    else
+                    {
+                        rflg = 0;
+                        aaa = a;
+                        bbb = b;
+                        y0 = y;
+                    }
+                    lgm = (yp*yp-3.0)/6.0;
+                    x = 2.0/(1.0/(2.0*aaa-1.0)+1.0/(2.0*bbb-1.0));
+                    d = yp*Math.Sqrt(x+lgm)/x-(1.0/(2.0*bbb-1.0)-1.0/(2.0*aaa-1.0))*(lgm+5.0/6.0-2.0/(3.0*x));
+                    d = 2.0*d;
+                    if( (double)(d)<(double)(Math.Log(math.minrealnumber)) )
+                    {
+                        x = 0;
+                        break;
+                    }
+                    x = aaa/(aaa+bbb*Math.Exp(d));
+                    yyy = incompletebeta(aaa, bbb, x);
+                    yp = (yyy-y0)/y0;
+                    if( (double)(Math.Abs(yp))<(double)(0.2) )
+                    {
+                        mainlooppos = newt;
+                        continue;
+                    }
+                    mainlooppos = ihalve;
+                    continue;
+                }
+                
+                //
+                // ihalve
+                //
+                if( mainlooppos==ihalve )
+                {
+                    dir = 0;
+                    di = 0.5;
+                    i = 0;
+                    mainlooppos = ihalvecycle;
+                    continue;
+                }
+                
+                //
+                // ihalvecycle
+                //
+                if( mainlooppos==ihalvecycle )
+                {
+                    if( i<=99 )
+                    {
+                        if( i!=0 )
+                        {
+                            x = x0+di*(x1-x0);
+                            if( (double)(x)==(double)(1.0) )
+                            {
+                                x = 1.0-math.machineepsilon;
+                            }
+                            if( (double)(x)==(double)(0.0) )
+                            {
+                                di = 0.5;
+                                x = x0+di*(x1-x0);
+                                if( (double)(x)==(double)(0.0) )
+                                {
+                                    break;
+                                }
+                            }
+                            yyy = incompletebeta(aaa, bbb, x);
+                            yp = (x1-x0)/(x1+x0);
+                            if( (double)(Math.Abs(yp))<(double)(dithresh) )
+                            {
+                                mainlooppos = newt;
+                                continue;
+                            }
+                            yp = (yyy-y0)/y0;
+                            if( (double)(Math.Abs(yp))<(double)(dithresh) )
+                            {
+                                mainlooppos = newt;
+                                continue;
+                            }
+                        }
+                        if( (double)(yyy)<(double)(y0) )
+                        {
+                            x0 = x;
+                            yl = yyy;
+                            if( dir<0 )
+                            {
+                                dir = 0;
+                                di = 0.5;
+                            }
+                            else
+                            {
+                                if( dir>3 )
+                                {
+                                    di = 1.0-(1.0-di)*(1.0-di);
+                                }
+                                else
+                                {
+                                    if( dir>1 )
+                                    {
+                                        di = 0.5*di+0.5;
+                                    }
+                                    else
+                                    {
+                                        di = (y0-yyy)/(yh-yl);
+                                    }
+                                }
+                            }
+                            dir = dir+1;
+                            if( (double)(x0)>(double)(0.75) )
+                            {
+                                if( rflg==1 )
+                                {
+                                    rflg = 0;
+                                    aaa = a;
+                                    bbb = b;
+                                    y0 = y;
+                                }
+                                else
+                                {
+                                    rflg = 1;
+                                    aaa = b;
+                                    bbb = a;
+                                    y0 = 1.0-y;
+                                }
+                                x = 1.0-x;
+                                yyy = incompletebeta(aaa, bbb, x);
+                                x0 = 0.0;
+                                yl = 0.0;
+                                x1 = 1.0;
+                                yh = 1.0;
+                                mainlooppos = ihalve;
+                                continue;
+                            }
+                        }
+                        else
+                        {
+                            x1 = x;
+                            if( rflg==1 && (double)(x1)<(double)(math.machineepsilon) )
+                            {
+                                x = 0.0;
+                                break;
+                            }
+                            yh = yyy;
+                            if( dir>0 )
+                            {
+                                dir = 0;
+                                di = 0.5;
+                            }
+                            else
+                            {
+                                if( dir<-3 )
+                                {
+                                    di = di*di;
+                                }
+                                else
+                                {
+                                    if( dir<-1 )
+                                    {
+                                        di = 0.5*di;
+                                    }
+                                    else
+                                    {
+                                        di = (yyy-y0)/(yh-yl);
+                                    }
+                                }
+                            }
+                            dir = dir-1;
+                        }
+                        i = i+1;
+                        mainlooppos = ihalvecycle;
+                        continue;
+                    }
+                    else
+                    {
+                        mainlooppos = breakihalvecycle;
+                        continue;
+                    }
+                }
+                
+                //
+                // breakihalvecycle
+                //
+                if( mainlooppos==breakihalvecycle )
+                {
+                    if( (double)(x0)>=(double)(1.0) )
+                    {
+                        x = 1.0-math.machineepsilon;
+                        break;
+                    }
+                    if( (double)(x)<=(double)(0.0) )
+                    {
+                        x = 0.0;
+                        break;
+                    }
+                    mainlooppos = newt;
+                    continue;
+                }
+                
+                //
+                // newt
+                //
+                if( mainlooppos==newt )
+                {
+                    if( nflg!=0 )
+                    {
+                        break;
+                    }
+                    nflg = 1;
+                    lgm = gammafunc.lngamma(aaa+bbb, ref s)-gammafunc.lngamma(aaa, ref s)-gammafunc.lngamma(bbb, ref s);
+                    i = 0;
+                    mainlooppos = newtcycle;
+                    continue;
+                }
+                
+                //
+                // newtcycle
+                //
+                if( mainlooppos==newtcycle )
+                {
+                    if( i<=7 )
+                    {
+                        if( i!=0 )
+                        {
+                            yyy = incompletebeta(aaa, bbb, x);
+                        }
+                        if( (double)(yyy)<(double)(yl) )
+                        {
+                            x = x0;
+                            yyy = yl;
+                        }
+                        else
+                        {
+                            if( (double)(yyy)>(double)(yh) )
+                            {
+                                x = x1;
+                                yyy = yh;
+                            }
+                            else
+                            {
+                                if( (double)(yyy)<(double)(y0) )
+                                {
+                                    x0 = x;
+                                    yl = yyy;
+                                }
+                                else
+                                {
+                                    x1 = x;
+                                    yh = yyy;
+                                }
+                            }
+                        }
+                        if( (double)(x)==(double)(1.0) || (double)(x)==(double)(0.0) )
+                        {
+                            mainlooppos = breaknewtcycle;
+                            continue;
+                        }
+                        d = (aaa-1.0)*Math.Log(x)+(bbb-1.0)*Math.Log(1.0-x)+lgm;
+                        if( (double)(d)<(double)(Math.Log(math.minrealnumber)) )
+                        {
+                            break;
+                        }
+                        if( (double)(d)>(double)(Math.Log(math.maxrealnumber)) )
+                        {
+                            mainlooppos = breaknewtcycle;
+                            continue;
+                        }
+                        d = Math.Exp(d);
+                        d = (yyy-y0)/d;
+                        xt = x-d;
+                        if( (double)(xt)<=(double)(x0) )
+                        {
+                            yyy = (x-x0)/(x1-x0);
+                            xt = x0+0.5*yyy*(x-x0);
+                            if( (double)(xt)<=(double)(0.0) )
+                            {
+                                mainlooppos = breaknewtcycle;
+                                continue;
+                            }
+                        }
+                        if( (double)(xt)>=(double)(x1) )
+                        {
+                            yyy = (x1-x)/(x1-x0);
+                            xt = x1-0.5*yyy*(x1-x);
+                            if( (double)(xt)>=(double)(1.0) )
+                            {
+                                mainlooppos = breaknewtcycle;
+                                continue;
+                            }
+                        }
+                        x = xt;
+                        if( (double)(Math.Abs(d/x))<(double)(128.0*math.machineepsilon) )
+                        {
+                            break;
+                        }
+                        i = i+1;
+                        mainlooppos = newtcycle;
+                        continue;
+                    }
+                    else
+                    {
+                        mainlooppos = breaknewtcycle;
+                        continue;
+                    }
+                }
+                
+                //
+                // breaknewtcycle
+                //
+                if( mainlooppos==breaknewtcycle )
+                {
+                    dithresh = 256.0*math.machineepsilon;
+                    mainlooppos = ihalve;
+                    continue;
+                }
+            }
+            
+            //
+            // done
+            //
+            if( rflg!=0 )
+            {
+                if( (double)(x)<=(double)(math.machineepsilon) )
+                {
+                    x = 1.0-math.machineepsilon;
+                }
+                else
+                {
+                    x = 1.0-x;
+                }
+            }
+            result = x;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Continued fraction expansion #1 for incomplete beta integral
+
+        Cephes Math Library, Release 2.8:  June, 2000
+        Copyright 1984, 1995, 2000 by Stephen L. Moshier
+        *************************************************************************/
+        private static double incompletebetafe(double a,
+            double b,
+            double x,
+            double big,
+            double biginv)
+        {
+            double result = 0;
+            double xk = 0;
+            double pk = 0;
+            double pkm1 = 0;
+            double pkm2 = 0;
+            double qk = 0;
+            double qkm1 = 0;
+            double qkm2 = 0;
+            double k1 = 0;
+            double k2 = 0;
+            double k3 = 0;
+            double k4 = 0;
+            double k5 = 0;
+            double k6 = 0;
+            double k7 = 0;
+            double k8 = 0;
+            double r = 0;
+            double t = 0;
+            double ans = 0;
+            double thresh = 0;
+            int n = 0;
+
+            k1 = a;
+            k2 = a+b;
+            k3 = a;
+            k4 = a+1.0;
+            k5 = 1.0;
+            k6 = b-1.0;
+            k7 = k4;
+            k8 = a+2.0;
+            pkm2 = 0.0;
+            qkm2 = 1.0;
+            pkm1 = 1.0;
+            qkm1 = 1.0;
+            ans = 1.0;
+            r = 1.0;
+            n = 0;
+            thresh = 3.0*math.machineepsilon;
+            do
+            {
+                xk = -(x*k1*k2/(k3*k4));
+                pk = pkm1+pkm2*xk;
+                qk = qkm1+qkm2*xk;
+                pkm2 = pkm1;
+                pkm1 = pk;
+                qkm2 = qkm1;
+                qkm1 = qk;
+                xk = x*k5*k6/(k7*k8);
+                pk = pkm1+pkm2*xk;
+                qk = qkm1+qkm2*xk;
+                pkm2 = pkm1;
+                pkm1 = pk;
+                qkm2 = qkm1;
+                qkm1 = qk;
+                if( (double)(qk)!=(double)(0) )
+                {
+                    r = pk/qk;
+                }
+                if( (double)(r)!=(double)(0) )
+                {
+                    t = Math.Abs((ans-r)/r);
+                    ans = r;
+                }
+                else
+                {
+                    t = 1.0;
+                }
+                if( (double)(t)<(double)(thresh) )
+                {
+                    break;
+                }
+                k1 = k1+1.0;
+                k2 = k2+1.0;
+                k3 = k3+2.0;
+                k4 = k4+2.0;
+                k5 = k5+1.0;
+                k6 = k6-1.0;
+                k7 = k7+2.0;
+                k8 = k8+2.0;
+                if( (double)(Math.Abs(qk)+Math.Abs(pk))>(double)(big) )
+                {
+                    pkm2 = pkm2*biginv;
+                    pkm1 = pkm1*biginv;
+                    qkm2 = qkm2*biginv;
+                    qkm1 = qkm1*biginv;
+                }
+                if( (double)(Math.Abs(qk))<(double)(biginv) || (double)(Math.Abs(pk))<(double)(biginv) )
+                {
+                    pkm2 = pkm2*big;
+                    pkm1 = pkm1*big;
+                    qkm2 = qkm2*big;
+                    qkm1 = qkm1*big;
+                }
+                n = n+1;
+            }
+            while( n!=300 );
+            result = ans;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Continued fraction expansion #2
+        for incomplete beta integral
+
+        Cephes Math Library, Release 2.8:  June, 2000
+        Copyright 1984, 1995, 2000 by Stephen L. Moshier
+        *************************************************************************/
+        private static double incompletebetafe2(double a,
+            double b,
+            double x,
+            double big,
+            double biginv)
+        {
+            double result = 0;
+            double xk = 0;
+            double pk = 0;
+            double pkm1 = 0;
+            double pkm2 = 0;
+            double qk = 0;
+            double qkm1 = 0;
+            double qkm2 = 0;
+            double k1 = 0;
+            double k2 = 0;
+            double k3 = 0;
+            double k4 = 0;
+            double k5 = 0;
+            double k6 = 0;
+            double k7 = 0;
+            double k8 = 0;
+            double r = 0;
+            double t = 0;
+            double ans = 0;
+            double z = 0;
+            double thresh = 0;
+            int n = 0;
+
+            k1 = a;
+            k2 = b-1.0;
+            k3 = a;
+            k4 = a+1.0;
+            k5 = 1.0;
+            k6 = a+b;
+            k7 = a+1.0;
+            k8 = a+2.0;
+            pkm2 = 0.0;
+            qkm2 = 1.0;
+            pkm1 = 1.0;
+            qkm1 = 1.0;
+            z = x/(1.0-x);
+            ans = 1.0;
+            r = 1.0;
+            n = 0;
+            thresh = 3.0*math.machineepsilon;
+            do
+            {
+                xk = -(z*k1*k2/(k3*k4));
+                pk = pkm1+pkm2*xk;
+                qk = qkm1+qkm2*xk;
+                pkm2 = pkm1;
+                pkm1 = pk;
+                qkm2 = qkm1;
+                qkm1 = qk;
+                xk = z*k5*k6/(k7*k8);
+                pk = pkm1+pkm2*xk;
+                qk = qkm1+qkm2*xk;
+                pkm2 = pkm1;
+                pkm1 = pk;
+                qkm2 = qkm1;
+                qkm1 = qk;
+                if( (double)(qk)!=(double)(0) )
+                {
+                    r = pk/qk;
+                }
+                if( (double)(r)!=(double)(0) )
+                {
+                    t = Math.Abs((ans-r)/r);
+                    ans = r;
+                }
+                else
+                {
+                    t = 1.0;
+                }
+                if( (double)(t)<(double)(thresh) )
+                {
+                    break;
+                }
+                k1 = k1+1.0;
+                k2 = k2-1.0;
+                k3 = k3+2.0;
+                k4 = k4+2.0;
+                k5 = k5+1.0;
+                k6 = k6+1.0;
+                k7 = k7+2.0;
+                k8 = k8+2.0;
+                if( (double)(Math.Abs(qk)+Math.Abs(pk))>(double)(big) )
+                {
+                    pkm2 = pkm2*biginv;
+                    pkm1 = pkm1*biginv;
+                    qkm2 = qkm2*biginv;
+                    qkm1 = qkm1*biginv;
+                }
+                if( (double)(Math.Abs(qk))<(double)(biginv) || (double)(Math.Abs(pk))<(double)(biginv) )
+                {
+                    pkm2 = pkm2*big;
+                    pkm1 = pkm1*big;
+                    qkm2 = qkm2*big;
+                    qkm1 = qkm1*big;
+                }
+                n = n+1;
+            }
+            while( n!=300 );
+            result = ans;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Power series for incomplete beta integral.
+        Use when b*x is small and x not too close to 1.
+
+        Cephes Math Library, Release 2.8:  June, 2000
+        Copyright 1984, 1995, 2000 by Stephen L. Moshier
+        *************************************************************************/
+        private static double incompletebetaps(double a,
+            double b,
+            double x,
+            double maxgam)
+        {
+            double result = 0;
+            double s = 0;
+            double t = 0;
+            double u = 0;
+            double v = 0;
+            double n = 0;
+            double t1 = 0;
+            double z = 0;
+            double ai = 0;
+            double sg = 0;
+
+            ai = 1.0/a;
+            u = (1.0-b)*x;
+            v = u/(a+1.0);
+            t1 = v;
+            t = u;
+            n = 2.0;
+            s = 0.0;
+            z = math.machineepsilon*ai;
+            while( (double)(Math.Abs(v))>(double)(z) )
+            {
+                u = (n-b)*x/n;
+                t = t*u;
+                v = t/(a+n);
+                s = s+v;
+                n = n+1.0;
+            }
+            s = s+t1;
+            s = s+ai;
+            u = a*Math.Log(x);
+            if( (double)(a+b)<(double)(maxgam) && (double)(Math.Abs(u))<(double)(Math.Log(math.maxrealnumber)) )
+            {
+                t = gammafunc.gammafunction(a+b)/(gammafunc.gammafunction(a)*gammafunc.gammafunction(b));
+                s = s*t*Math.Pow(x, a);
+            }
+            else
+            {
+                t = gammafunc.lngamma(a+b, ref sg)-gammafunc.lngamma(a, ref sg)-gammafunc.lngamma(b, ref sg)+u+Math.Log(s);
+                if( (double)(t)<(double)(Math.Log(math.minrealnumber)) )
+                {
+                    s = 0.0;
+                }
+                else
+                {
+                    s = Math.Exp(t);
+                }
+            }
+            result = s;
+            return result;
+        }
+
+
+    }
+    public class binomialdistr
+    {
+        /*************************************************************************
+        Binomial distribution
+
+        Returns the sum of the terms 0 through k of the Binomial
+        probability density:
+
+          k
+          --  ( n )   j      n-j
+          >   (   )  p  (1-p)
+          --  ( j )
+         j=0
+
+        The terms are not summed directly; instead the incomplete
+        beta integral is employed, according to the formula
+
+        y = bdtr( k, n, p ) = incbet( n-k, k+1, 1-p ).
+
+        The arguments must be positive, with p ranging from 0 to 1.
+
+        ACCURACY:
+
+        Tested at random points (a,b,p), with p between 0 and 1.
+
+                      a,b                     Relative error:
+        arithmetic  domain     # trials      peak         rms
+         For p between 0.001 and 1:
+           IEEE     0,100       100000      4.3e-15     2.6e-16
+
+        Cephes Math Library Release 2.8:  June, 2000
+        Copyright 1984, 1987, 1995, 2000 by Stephen L. Moshier
+        *************************************************************************/
+        public static double binomialdistribution(int k,
+            int n,
+            double p)
+        {
+            double result = 0;
+            double dk = 0;
+            double dn = 0;
+
+            alglib.ap.assert((double)(p)>=(double)(0) && (double)(p)<=(double)(1), "Domain error in BinomialDistribution");
+            alglib.ap.assert(k>=-1 && k<=n, "Domain error in BinomialDistribution");
+            if( k==-1 )
+            {
+                result = 0;
+                return result;
+            }
+            if( k==n )
+            {
+                result = 1;
+                return result;
+            }
+            dn = n-k;
+            if( k==0 )
+            {
+                dk = Math.Pow(1.0-p, dn);
+            }
+            else
+            {
+                dk = k+1;
+                dk = ibetaf.incompletebeta(dn, dk, 1.0-p);
+            }
+            result = dk;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Complemented binomial distribution
+
+        Returns the sum of the terms k+1 through n of the Binomial
+        probability density:
+
+          n
+          --  ( n )   j      n-j
+          >   (   )  p  (1-p)
+          --  ( j )
+         j=k+1
+
+        The terms are not summed directly; instead the incomplete
+        beta integral is employed, according to the formula
+
+        y = bdtrc( k, n, p ) = incbet( k+1, n-k, p ).
+
+        The arguments must be positive, with p ranging from 0 to 1.
+
+        ACCURACY:
+
+        Tested at random points (a,b,p).
+
+                      a,b                     Relative error:
+        arithmetic  domain     # trials      peak         rms
+         For p between 0.001 and 1:
+           IEEE     0,100       100000      6.7e-15     8.2e-16
+         For p between 0 and .001:
+           IEEE     0,100       100000      1.5e-13     2.7e-15
+
+        Cephes Math Library Release 2.8:  June, 2000
+        Copyright 1984, 1987, 1995, 2000 by Stephen L. Moshier
+        *************************************************************************/
+        public static double binomialcdistribution(int k,
+            int n,
+            double p)
+        {
+            double result = 0;
+            double dk = 0;
+            double dn = 0;
+
+            alglib.ap.assert((double)(p)>=(double)(0) && (double)(p)<=(double)(1), "Domain error in BinomialDistributionC");
+            alglib.ap.assert(k>=-1 && k<=n, "Domain error in BinomialDistributionC");
+            if( k==-1 )
+            {
+                result = 1;
+                return result;
+            }
+            if( k==n )
+            {
+                result = 0;
+                return result;
+            }
+            dn = n-k;
+            if( k==0 )
+            {
+                if( (double)(p)<(double)(0.01) )
+                {
+                    dk = -nearunityunit.nuexpm1(dn*nearunityunit.nulog1p(-p));
+                }
+                else
+                {
+                    dk = 1.0-Math.Pow(1.0-p, dn);
+                }
+            }
+            else
+            {
+                dk = k+1;
+                dk = ibetaf.incompletebeta(dk, dn, p);
+            }
+            result = dk;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Inverse binomial distribution
+
+        Finds the event probability p such that the sum of the
+        terms 0 through k of the Binomial probability density
+        is equal to the given cumulative probability y.
+
+        This is accomplished using the inverse beta integral
+        function and the relation
+
+        1 - p = incbi( n-k, k+1, y ).
+
+        ACCURACY:
+
+        Tested at random points (a,b,p).
+
+                      a,b                     Relative error:
+        arithmetic  domain     # trials      peak         rms
+         For p between 0.001 and 1:
+           IEEE     0,100       100000      2.3e-14     6.4e-16
+           IEEE     0,10000     100000      6.6e-12     1.2e-13
+         For p between 10^-6 and 0.001:
+           IEEE     0,100       100000      2.0e-12     1.3e-14
+           IEEE     0,10000     100000      1.5e-12     3.2e-14
+
+        Cephes Math Library Release 2.8:  June, 2000
+        Copyright 1984, 1987, 1995, 2000 by Stephen L. Moshier
+        *************************************************************************/
+        public static double invbinomialdistribution(int k,
+            int n,
+            double y)
+        {
+            double result = 0;
+            double dk = 0;
+            double dn = 0;
+            double p = 0;
+
+            alglib.ap.assert(k>=0 && k<n, "Domain error in InvBinomialDistribution");
+            dn = n-k;
+            if( k==0 )
+            {
+                if( (double)(y)>(double)(0.8) )
+                {
+                    p = -nearunityunit.nuexpm1(nearunityunit.nulog1p(y-1.0)/dn);
+                }
+                else
+                {
+                    p = 1.0-Math.Pow(y, 1.0/dn);
+                }
+            }
+            else
+            {
+                dk = k+1;
+                p = ibetaf.incompletebeta(dn, dk, 0.5);
+                if( (double)(p)>(double)(0.5) )
+                {
+                    p = ibetaf.invincompletebeta(dk, dn, 1.0-y);
+                }
+                else
+                {
+                    p = 1.0-ibetaf.invincompletebeta(dn, dk, y);
+                }
+            }
+            result = p;
+            return result;
+        }
+
+
+    }
+    public class chebyshev
+    {
+        /*************************************************************************
+        Calculation of the value of the Chebyshev polynomials of the
+        first and second kinds.
+
+        Parameters:
+            r   -   polynomial kind, either 1 or 2.
+            n   -   degree, n>=0
+            x   -   argument, -1 <= x <= 1
+
+        Result:
+            the value of the Chebyshev polynomial at x
+        *************************************************************************/
+        public static double chebyshevcalculate(int r,
+            int n,
+            double x)
+        {
+            double result = 0;
+            int i = 0;
+            double a = 0;
+            double b = 0;
+
+            result = 0;
+            
+            //
+            // Prepare A and B
+            //
+            if( r==1 )
+            {
+                a = 1;
+                b = x;
+            }
+            else
+            {
+                a = 1;
+                b = 2*x;
+            }
+            
+            //
+            // Special cases: N=0 or N=1
+            //
+            if( n==0 )
+            {
+                result = a;
+                return result;
+            }
+            if( n==1 )
+            {
+                result = b;
+                return result;
+            }
+            
+            //
+            // General case: N>=2
+            //
+            for(i=2; i<=n; i++)
+            {
+                result = 2*x*b-a;
+                a = b;
+                b = result;
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        Summation of Chebyshev polynomials using Clenshaws recurrence formula.
+
+        This routine calculates
+            c[0]*T0(x) + c[1]*T1(x) + ... + c[N]*TN(x)
+        or
+            c[0]*U0(x) + c[1]*U1(x) + ... + c[N]*UN(x)
+        depending on the R.
+
+        Parameters:
+            r   -   polynomial kind, either 1 or 2.
+            n   -   degree, n>=0
+            x   -   argument
+
+        Result:
+            the value of the Chebyshev polynomial at x
+        *************************************************************************/
+        public static double chebyshevsum(double[] c,
+            int r,
+            int n,
+            double x)
+        {
+            double result = 0;
+            double b1 = 0;
+            double b2 = 0;
+            int i = 0;
+
+            b1 = 0;
+            b2 = 0;
+            for(i=n; i>=1; i--)
+            {
+                result = 2*x*b1-b2+c[i];
+                b2 = b1;
+                b1 = result;
+            }
+            if( r==1 )
+            {
+                result = -b2+x*b1+c[0];
+            }
+            else
+            {
+                result = -b2+2*x*b1+c[0];
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        Representation of Tn as C[0] + C[1]*X + ... + C[N]*X^N
+
+        Input parameters:
+            N   -   polynomial degree, n>=0
+
+        Output parameters:
+            C   -   coefficients
+        *************************************************************************/
+        public static void chebyshevcoefficients(int n,
+            ref double[] c)
+        {
+            int i = 0;
+
+            c = new double[0];
+
+            c = new double[n+1];
+            for(i=0; i<=n; i++)
+            {
+                c[i] = 0;
+            }
+            if( n==0 || n==1 )
+            {
+                c[n] = 1;
+            }
+            else
+            {
+                c[n] = Math.Exp((n-1)*Math.Log(2));
+                for(i=0; i<=n/2-1; i++)
+                {
+                    c[n-2*(i+1)] = -(c[n-2*i]*(n-2*i)*(n-2*i-1)/4/(i+1)/(n-i-1));
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Conversion of a series of Chebyshev polynomials to a power series.
+
+        Represents A[0]*T0(x) + A[1]*T1(x) + ... + A[N]*Tn(x) as
+        B[0] + B[1]*X + ... + B[N]*X^N.
+
+        Input parameters:
+            A   -   Chebyshev series coefficients
+            N   -   degree, N>=0
+            
+        Output parameters
+            B   -   power series coefficients
+        *************************************************************************/
+        public static void fromchebyshev(double[] a,
+            int n,
+            ref double[] b)
+        {
+            int i = 0;
+            int k = 0;
+            double e = 0;
+            double d = 0;
+
+            b = new double[0];
+
+            b = new double[n+1];
+            for(i=0; i<=n; i++)
+            {
+                b[i] = 0;
+            }
+            d = 0;
+            i = 0;
+            do
+            {
+                k = i;
+                do
+                {
+                    e = b[k];
+                    b[k] = 0;
+                    if( i<=1 && k==i )
+                    {
+                        b[k] = 1;
+                    }
+                    else
+                    {
+                        if( i!=0 )
+                        {
+                            b[k] = 2*d;
+                        }
+                        if( k>i+1 )
+                        {
+                            b[k] = b[k]-b[k-2];
+                        }
+                    }
+                    d = e;
+                    k = k+1;
+                }
+                while( k<=n );
+                d = b[i];
+                e = 0;
+                k = i;
+                while( k<=n )
+                {
+                    e = e+b[k]*a[k];
+                    k = k+2;
+                }
+                b[i] = e;
+                i = i+1;
+            }
+            while( i<=n );
+        }
+
+
+    }
+    public class chisquaredistr
+    {
+        /*************************************************************************
+        Chi-square distribution
+
+        Returns the area under the left hand tail (from 0 to x)
+        of the Chi square probability density function with
+        v degrees of freedom.
+
+
+                                          x
+                                           -
+                               1          | |  v/2-1  -t/2
+         P( x | v )   =   -----------     |   t      e     dt
+                           v/2  -       | |
+                          2    | (v/2)   -
+                                          0
+
+        where x is the Chi-square variable.
+
+        The incomplete gamma integral is used, according to the
+        formula
+
+        y = chdtr( v, x ) = igam( v/2.0, x/2.0 ).
+
+        The arguments must both be positive.
+
+        ACCURACY:
+
+        See incomplete gamma function
+
+
+        Cephes Math Library Release 2.8:  June, 2000
+        Copyright 1984, 1987, 2000 by Stephen L. Moshier
+        *************************************************************************/
+        public static double chisquaredistribution(double v,
+            double x)
+        {
+            double result = 0;
+
+            alglib.ap.assert((double)(x)>=(double)(0) && (double)(v)>=(double)(1), "Domain error in ChiSquareDistribution");
+            result = igammaf.incompletegamma(v/2.0, x/2.0);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Complemented Chi-square distribution
+
+        Returns the area under the right hand tail (from x to
+        infinity) of the Chi square probability density function
+        with v degrees of freedom:
+
+                                         inf.
+                                           -
+                               1          | |  v/2-1  -t/2
+         P( x | v )   =   -----------     |   t      e     dt
+                           v/2  -       | |
+                          2    | (v/2)   -
+                                          x
+
+        where x is the Chi-square variable.
+
+        The incomplete gamma integral is used, according to the
+        formula
+
+        y = chdtr( v, x ) = igamc( v/2.0, x/2.0 ).
+
+        The arguments must both be positive.
+
+        ACCURACY:
+
+        See incomplete gamma function
+
+        Cephes Math Library Release 2.8:  June, 2000
+        Copyright 1984, 1987, 2000 by Stephen L. Moshier
+        *************************************************************************/
+        public static double chisquarecdistribution(double v,
+            double x)
+        {
+            double result = 0;
+
+            alglib.ap.assert((double)(x)>=(double)(0) && (double)(v)>=(double)(1), "Domain error in ChiSquareDistributionC");
+            result = igammaf.incompletegammac(v/2.0, x/2.0);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Inverse of complemented Chi-square distribution
+
+        Finds the Chi-square argument x such that the integral
+        from x to infinity of the Chi-square density is equal
+        to the given cumulative probability y.
+
+        This is accomplished using the inverse gamma integral
+        function and the relation
+
+           x/2 = igami( df/2, y );
+
+        ACCURACY:
+
+        See inverse incomplete gamma function
+
+
+        Cephes Math Library Release 2.8:  June, 2000
+        Copyright 1984, 1987, 2000 by Stephen L. Moshier
+        *************************************************************************/
+        public static double invchisquaredistribution(double v,
+            double y)
+        {
+            double result = 0;
+
+            alglib.ap.assert(((double)(y)>=(double)(0) && (double)(y)<=(double)(1)) && (double)(v)>=(double)(1), "Domain error in InvChiSquareDistribution");
+            result = 2*igammaf.invincompletegammac(0.5*v, y);
+            return result;
+        }
+
+
+    }
+    public class dawson
+    {
+        /*************************************************************************
+        Dawson's Integral
+
+        Approximates the integral
+
+                                    x
+                                    -
+                             2     | |        2
+         dawsn(x)  =  exp( -x  )   |    exp( t  ) dt
+                                 | |
+                                  -
+                                  0
+
+        Three different rational approximations are employed, for
+        the intervals 0 to 3.25; 3.25 to 6.25; and 6.25 up.
+
+        ACCURACY:
+
+                             Relative error:
+        arithmetic   domain     # trials      peak         rms
+           IEEE      0,10        10000       6.9e-16     1.0e-16
+
+        Cephes Math Library Release 2.8:  June, 2000
+        Copyright 1984, 1987, 1989, 2000 by Stephen L. Moshier
+        *************************************************************************/
+        public static double dawsonintegral(double x)
+        {
+            double result = 0;
+            double x2 = 0;
+            double y = 0;
+            int sg = 0;
+            double an = 0;
+            double ad = 0;
+            double bn = 0;
+            double bd = 0;
+            double cn = 0;
+            double cd = 0;
+
+            sg = 1;
+            if( (double)(x)<(double)(0) )
+            {
+                sg = -1;
+                x = -x;
+            }
+            if( (double)(x)<(double)(3.25) )
+            {
+                x2 = x*x;
+                an = 1.13681498971755972054E-11;
+                an = an*x2+8.49262267667473811108E-10;
+                an = an*x2+1.94434204175553054283E-8;
+                an = an*x2+9.53151741254484363489E-7;
+                an = an*x2+3.07828309874913200438E-6;
+                an = an*x2+3.52513368520288738649E-4;
+                an = an*x2+-8.50149846724410912031E-4;
+                an = an*x2+4.22618223005546594270E-2;
+                an = an*x2+-9.17480371773452345351E-2;
+                an = an*x2+9.99999999999999994612E-1;
+                ad = 2.40372073066762605484E-11;
+                ad = ad*x2+1.48864681368493396752E-9;
+                ad = ad*x2+5.21265281010541664570E-8;
+                ad = ad*x2+1.27258478273186970203E-6;
+                ad = ad*x2+2.32490249820789513991E-5;
+                ad = ad*x2+3.25524741826057911661E-4;
+                ad = ad*x2+3.48805814657162590916E-3;
+                ad = ad*x2+2.79448531198828973716E-2;
+                ad = ad*x2+1.58874241960120565368E-1;
+                ad = ad*x2+5.74918629489320327824E-1;
+                ad = ad*x2+1.00000000000000000539E0;
+                y = x*an/ad;
+                result = sg*y;
+                return result;
+            }
+            x2 = 1.0/(x*x);
+            if( (double)(x)<(double)(6.25) )
+            {
+                bn = 5.08955156417900903354E-1;
+                bn = bn*x2-2.44754418142697847934E-1;
+                bn = bn*x2+9.41512335303534411857E-2;
+                bn = bn*x2-2.18711255142039025206E-2;
+                bn = bn*x2+3.66207612329569181322E-3;
+                bn = bn*x2-4.23209114460388756528E-4;
+                bn = bn*x2+3.59641304793896631888E-5;
+                bn = bn*x2-2.14640351719968974225E-6;
+                bn = bn*x2+9.10010780076391431042E-8;
+                bn = bn*x2-2.40274520828250956942E-9;
+                bn = bn*x2+3.59233385440928410398E-11;
+                bd = 1.00000000000000000000E0;
+                bd = bd*x2-6.31839869873368190192E-1;
+                bd = bd*x2+2.36706788228248691528E-1;
+                bd = bd*x2-5.31806367003223277662E-2;
+                bd = bd*x2+8.48041718586295374409E-3;
+                bd = bd*x2-9.47996768486665330168E-4;
+                bd = bd*x2+7.81025592944552338085E-5;
+                bd = bd*x2-4.55875153252442634831E-6;
+                bd = bd*x2+1.89100358111421846170E-7;
+                bd = bd*x2-4.91324691331920606875E-9;
+                bd = bd*x2+7.18466403235734541950E-11;
+                y = 1.0/x+x2*bn/(bd*x);
+                result = sg*0.5*y;
+                return result;
+            }
+            if( (double)(x)>(double)(1.0E9) )
+            {
+                result = sg*0.5/x;
+                return result;
+            }
+            cn = -5.90592860534773254987E-1;
+            cn = cn*x2+6.29235242724368800674E-1;
+            cn = cn*x2-1.72858975380388136411E-1;
+            cn = cn*x2+1.64837047825189632310E-2;
+            cn = cn*x2-4.86827613020462700845E-4;
+            cd = 1.00000000000000000000E0;
+            cd = cd*x2-2.69820057197544900361E0;
+            cd = cd*x2+1.73270799045947845857E0;
+            cd = cd*x2-3.93708582281939493482E-1;
+            cd = cd*x2+3.44278924041233391079E-2;
+            cd = cd*x2-9.73655226040941223894E-4;
+            y = 1.0/x+x2*cn/(cd*x);
+            result = sg*0.5*y;
+            return result;
+        }
+
+
+    }
+    public class elliptic
+    {
+        /*************************************************************************
+        Complete elliptic integral of the first kind
+
+        Approximates the integral
+
+
+
+                   pi/2
+                    -
+                   | |
+                   |           dt
+        K(m)  =    |    ------------------
+                   |                   2
+                 | |    sqrt( 1 - m sin t )
+                  -
+                   0
+
+        using the approximation
+
+            P(x)  -  log x Q(x).
+
+        ACCURACY:
+
+                             Relative error:
+        arithmetic   domain     # trials      peak         rms
+           IEEE       0,1        30000       2.5e-16     6.8e-17
+
+        Cephes Math Library, Release 2.8:  June, 2000
+        Copyright 1984, 1987, 2000 by Stephen L. Moshier
+        *************************************************************************/
+        public static double ellipticintegralk(double m)
+        {
+            double result = 0;
+
+            result = ellipticintegralkhighprecision(1.0-m);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Complete elliptic integral of the first kind
+
+        Approximates the integral
+
+
+
+                   pi/2
+                    -
+                   | |
+                   |           dt
+        K(m)  =    |    ------------------
+                   |                   2
+                 | |    sqrt( 1 - m sin t )
+                  -
+                   0
+
+        where m = 1 - m1, using the approximation
+
+            P(x)  -  log x Q(x).
+
+        The argument m1 is used rather than m so that the logarithmic
+        singularity at m = 1 will be shifted to the origin; this
+        preserves maximum accuracy.
+
+        K(0) = pi/2.
+
+        ACCURACY:
+
+                             Relative error:
+        arithmetic   domain     # trials      peak         rms
+           IEEE       0,1        30000       2.5e-16     6.8e-17
+
+        Àëãîðèòì âçÿò èç áèáëèîòåêè Cephes
+        *************************************************************************/
+        public static double ellipticintegralkhighprecision(double m1)
+        {
+            double result = 0;
+            double p = 0;
+            double q = 0;
+
+            if( (double)(m1)<=(double)(math.machineepsilon) )
+            {
+                result = 1.3862943611198906188E0-0.5*Math.Log(m1);
+            }
+            else
+            {
+                p = 1.37982864606273237150E-4;
+                p = p*m1+2.28025724005875567385E-3;
+                p = p*m1+7.97404013220415179367E-3;
+                p = p*m1+9.85821379021226008714E-3;
+                p = p*m1+6.87489687449949877925E-3;
+                p = p*m1+6.18901033637687613229E-3;
+                p = p*m1+8.79078273952743772254E-3;
+                p = p*m1+1.49380448916805252718E-2;
+                p = p*m1+3.08851465246711995998E-2;
+                p = p*m1+9.65735902811690126535E-2;
+                p = p*m1+1.38629436111989062502E0;
+                q = 2.94078955048598507511E-5;
+                q = q*m1+9.14184723865917226571E-4;
+                q = q*m1+5.94058303753167793257E-3;
+                q = q*m1+1.54850516649762399335E-2;
+                q = q*m1+2.39089602715924892727E-2;
+                q = q*m1+3.01204715227604046988E-2;
+                q = q*m1+3.73774314173823228969E-2;
+                q = q*m1+4.88280347570998239232E-2;
+                q = q*m1+7.03124996963957469739E-2;
+                q = q*m1+1.24999999999870820058E-1;
+                q = q*m1+4.99999999999999999821E-1;
+                result = p-q*Math.Log(m1);
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        Incomplete elliptic integral of the first kind F(phi|m)
+
+        Approximates the integral
+
+
+
+                       phi
+                        -
+                       | |
+                       |           dt
+        F(phi_\m)  =    |    ------------------
+                       |                   2
+                     | |    sqrt( 1 - m sin t )
+                      -
+                       0
+
+        of amplitude phi and modulus m, using the arithmetic -
+        geometric mean algorithm.
+
+
+
+
+        ACCURACY:
+
+        Tested at random points with m in [0, 1] and phi as indicated.
+
+                             Relative error:
+        arithmetic   domain     # trials      peak         rms
+           IEEE     -10,10       200000      7.4e-16     1.0e-16
+
+        Cephes Math Library Release 2.8:  June, 2000
+        Copyright 1984, 1987, 2000 by Stephen L. Moshier
+        *************************************************************************/
+        public static double incompleteellipticintegralk(double phi,
+            double m)
+        {
+            double result = 0;
+            double a = 0;
+            double b = 0;
+            double c = 0;
+            double e = 0;
+            double temp = 0;
+            double pio2 = 0;
+            double t = 0;
+            double k = 0;
+            int d = 0;
+            int md = 0;
+            int s = 0;
+            int npio2 = 0;
+
+            pio2 = 1.57079632679489661923;
+            if( (double)(m)==(double)(0) )
+            {
+                result = phi;
+                return result;
+            }
+            a = 1-m;
+            if( (double)(a)==(double)(0) )
+            {
+                result = Math.Log(Math.Tan(0.5*(pio2+phi)));
+                return result;
+            }
+            npio2 = (int)Math.Floor(phi/pio2);
+            if( npio2%2!=0 )
+            {
+                npio2 = npio2+1;
+            }
+            if( npio2!=0 )
+            {
+                k = ellipticintegralk(1-a);
+                phi = phi-npio2*pio2;
+            }
+            else
+            {
+                k = 0;
+            }
+            if( (double)(phi)<(double)(0) )
+            {
+                phi = -phi;
+                s = -1;
+            }
+            else
+            {
+                s = 0;
+            }
+            b = Math.Sqrt(a);
+            t = Math.Tan(phi);
+            if( (double)(Math.Abs(t))>(double)(10) )
+            {
+                e = 1.0/(b*t);
+                if( (double)(Math.Abs(e))<(double)(10) )
+                {
+                    e = Math.Atan(e);
+                    if( npio2==0 )
+                    {
+                        k = ellipticintegralk(1-a);
+                    }
+                    temp = k-incompleteellipticintegralk(e, m);
+                    if( s<0 )
+                    {
+                        temp = -temp;
+                    }
+                    result = temp+npio2*k;
+                    return result;
+                }
+            }
+            a = 1.0;
+            c = Math.Sqrt(m);
+            d = 1;
+            md = 0;
+            while( (double)(Math.Abs(c/a))>(double)(math.machineepsilon) )
+            {
+                temp = b/a;
+                phi = phi+Math.Atan(t*temp)+md*Math.PI;
+                md = (int)((phi+pio2)/Math.PI);
+                t = t*(1.0+temp)/(1.0-temp*t*t);
+                c = 0.5*(a-b);
+                temp = Math.Sqrt(a*b);
+                a = 0.5*(a+b);
+                b = temp;
+                d = d+d;
+            }
+            temp = (Math.Atan(t)+md*Math.PI)/(d*a);
+            if( s<0 )
+            {
+                temp = -temp;
+            }
+            result = temp+npio2*k;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Complete elliptic integral of the second kind
+
+        Approximates the integral
+
+
+                   pi/2
+                    -
+                   | |                 2
+        E(m)  =    |    sqrt( 1 - m sin t ) dt
+                 | |
+                  -
+                   0
+
+        using the approximation
+
+             P(x)  -  x log x Q(x).
+
+        ACCURACY:
+
+                             Relative error:
+        arithmetic   domain     # trials      peak         rms
+           IEEE       0, 1       10000       2.1e-16     7.3e-17
+
+        Cephes Math Library, Release 2.8: June, 2000
+        Copyright 1984, 1987, 1989, 2000 by Stephen L. Moshier
+        *************************************************************************/
+        public static double ellipticintegrale(double m)
+        {
+            double result = 0;
+            double p = 0;
+            double q = 0;
+
+            alglib.ap.assert((double)(m)>=(double)(0) && (double)(m)<=(double)(1), "Domain error in EllipticIntegralE: m<0 or m>1");
+            m = 1-m;
+            if( (double)(m)==(double)(0) )
+            {
+                result = 1;
+                return result;
+            }
+            p = 1.53552577301013293365E-4;
+            p = p*m+2.50888492163602060990E-3;
+            p = p*m+8.68786816565889628429E-3;
+            p = p*m+1.07350949056076193403E-2;
+            p = p*m+7.77395492516787092951E-3;
+            p = p*m+7.58395289413514708519E-3;
+            p = p*m+1.15688436810574127319E-2;
+            p = p*m+2.18317996015557253103E-2;
+            p = p*m+5.68051945617860553470E-2;
+            p = p*m+4.43147180560990850618E-1;
+            p = p*m+1.00000000000000000299E0;
+            q = 3.27954898576485872656E-5;
+            q = q*m+1.00962792679356715133E-3;
+            q = q*m+6.50609489976927491433E-3;
+            q = q*m+1.68862163993311317300E-2;
+            q = q*m+2.61769742454493659583E-2;
+            q = q*m+3.34833904888224918614E-2;
+            q = q*m+4.27180926518931511717E-2;
+            q = q*m+5.85936634471101055642E-2;
+            q = q*m+9.37499997197644278445E-2;
+            q = q*m+2.49999999999888314361E-1;
+            result = p-q*m*Math.Log(m);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Incomplete elliptic integral of the second kind
+
+        Approximates the integral
+
+
+                       phi
+                        -
+                       | |
+                       |                   2
+        E(phi_\m)  =    |    sqrt( 1 - m sin t ) dt
+                       |
+                     | |
+                      -
+                       0
+
+        of amplitude phi and modulus m, using the arithmetic -
+        geometric mean algorithm.
+
+        ACCURACY:
+
+        Tested at random arguments with phi in [-10, 10] and m in
+        [0, 1].
+                             Relative error:
+        arithmetic   domain     # trials      peak         rms
+           IEEE     -10,10      150000       3.3e-15     1.4e-16
+
+        Cephes Math Library Release 2.8:  June, 2000
+        Copyright 1984, 1987, 1993, 2000 by Stephen L. Moshier
+        *************************************************************************/
+        public static double incompleteellipticintegrale(double phi,
+            double m)
+        {
+            double result = 0;
+            double pio2 = 0;
+            double a = 0;
+            double b = 0;
+            double c = 0;
+            double e = 0;
+            double temp = 0;
+            double lphi = 0;
+            double t = 0;
+            double ebig = 0;
+            int d = 0;
+            int md = 0;
+            int npio2 = 0;
+            int s = 0;
+
+            pio2 = 1.57079632679489661923;
+            if( (double)(m)==(double)(0) )
+            {
+                result = phi;
+                return result;
+            }
+            lphi = phi;
+            npio2 = (int)Math.Floor(lphi/pio2);
+            if( npio2%2!=0 )
+            {
+                npio2 = npio2+1;
+            }
+            lphi = lphi-npio2*pio2;
+            if( (double)(lphi)<(double)(0) )
+            {
+                lphi = -lphi;
+                s = -1;
+            }
+            else
+            {
+                s = 1;
+            }
+            a = 1.0-m;
+            ebig = ellipticintegrale(m);
+            if( (double)(a)==(double)(0) )
+            {
+                temp = Math.Sin(lphi);
+                if( s<0 )
+                {
+                    temp = -temp;
+                }
+                result = temp+npio2*ebig;
+                return result;
+            }
+            t = Math.Tan(lphi);
+            b = Math.Sqrt(a);
+            
+            //
+            // Thanks to Brian Fitzgerald <fitzgb@mml0.meche.rpi.edu>
+            // for pointing out an instability near odd multiples of pi/2
+            //
+            if( (double)(Math.Abs(t))>(double)(10) )
+            {
+                
+                //
+                // Transform the amplitude
+                //
+                e = 1.0/(b*t);
+                
+                //
+                // ... but avoid multiple recursions.
+                //
+                if( (double)(Math.Abs(e))<(double)(10) )
+                {
+                    e = Math.Atan(e);
+                    temp = ebig+m*Math.Sin(lphi)*Math.Sin(e)-incompleteellipticintegrale(e, m);
+                    if( s<0 )
+                    {
+                        temp = -temp;
+                    }
+                    result = temp+npio2*ebig;
+                    return result;
+                }
+            }
+            c = Math.Sqrt(m);
+            a = 1.0;
+            d = 1;
+            e = 0.0;
+            md = 0;
+            while( (double)(Math.Abs(c/a))>(double)(math.machineepsilon) )
+            {
+                temp = b/a;
+                lphi = lphi+Math.Atan(t*temp)+md*Math.PI;
+                md = (int)((lphi+pio2)/Math.PI);
+                t = t*(1.0+temp)/(1.0-temp*t*t);
+                c = 0.5*(a-b);
+                temp = Math.Sqrt(a*b);
+                a = 0.5*(a+b);
+                b = temp;
+                d = d+d;
+                e = e+c*Math.Sin(lphi);
+            }
+            temp = ebig/ellipticintegralk(m);
+            temp = temp*((Math.Atan(t)+md*Math.PI)/(d*a));
+            temp = temp+e;
+            if( s<0 )
+            {
+                temp = -temp;
+            }
+            result = temp+npio2*ebig;
+            return result;
+        }
+
+
+    }
+    public class expintegrals
+    {
+        /*************************************************************************
+        Exponential integral Ei(x)
+
+                      x
+                       -     t
+                      | |   e
+           Ei(x) =   -|-   ---  dt .
+                    | |     t
+                     -
+                    -inf
+
+        Not defined for x <= 0.
+        See also expn.c.
+
+
+
+        ACCURACY:
+
+                             Relative error:
+        arithmetic   domain     # trials      peak         rms
+           IEEE       0,100       50000      8.6e-16     1.3e-16
+
+        Cephes Math Library Release 2.8:  May, 1999
+        Copyright 1999 by Stephen L. Moshier
+        *************************************************************************/
+        public static double exponentialintegralei(double x)
+        {
+            double result = 0;
+            double eul = 0;
+            double f = 0;
+            double f1 = 0;
+            double f2 = 0;
+            double w = 0;
+
+            eul = 0.5772156649015328606065;
+            if( (double)(x)<=(double)(0) )
+            {
+                result = 0;
+                return result;
+            }
+            if( (double)(x)<(double)(2) )
+            {
+                f1 = -5.350447357812542947283;
+                f1 = f1*x+218.5049168816613393830;
+                f1 = f1*x-4176.572384826693777058;
+                f1 = f1*x+55411.76756393557601232;
+                f1 = f1*x-331338.1331178144034309;
+                f1 = f1*x+1592627.163384945414220;
+                f2 = 1.000000000000000000000;
+                f2 = f2*x-52.50547959112862969197;
+                f2 = f2*x+1259.616186786790571525;
+                f2 = f2*x-17565.49581973534652631;
+                f2 = f2*x+149306.2117002725991967;
+                f2 = f2*x-729494.9239640527645655;
+                f2 = f2*x+1592627.163384945429726;
+                f = f1/f2;
+                result = eul+Math.Log(x)+x*f;
+                return result;
+            }
+            if( (double)(x)<(double)(4) )
+            {
+                w = 1/x;
+                f1 = 1.981808503259689673238E-2;
+                f1 = f1*w-1.271645625984917501326;
+                f1 = f1*w-2.088160335681228318920;
+                f1 = f1*w+2.755544509187936721172;
+                f1 = f1*w-4.409507048701600257171E-1;
+                f1 = f1*w+4.665623805935891391017E-2;
+                f1 = f1*w-1.545042679673485262580E-3;
+                f1 = f1*w+7.059980605299617478514E-5;
+                f2 = 1.000000000000000000000;
+                f2 = f2*w+1.476498670914921440652;
+                f2 = f2*w+5.629177174822436244827E-1;
+                f2 = f2*w+1.699017897879307263248E-1;
+                f2 = f2*w+2.291647179034212017463E-2;
+                f2 = f2*w+4.450150439728752875043E-3;
+                f2 = f2*w+1.727439612206521482874E-4;
+                f2 = f2*w+3.953167195549672482304E-5;
+                f = f1/f2;
+                result = Math.Exp(x)*w*(1+w*f);
+                return result;
+            }
+            if( (double)(x)<(double)(8) )
+            {
+                w = 1/x;
+                f1 = -1.373215375871208729803;
+                f1 = f1*w-7.084559133740838761406E-1;
+                f1 = f1*w+1.580806855547941010501;
+                f1 = f1*w-2.601500427425622944234E-1;
+                f1 = f1*w+2.994674694113713763365E-2;
+                f1 = f1*w-1.038086040188744005513E-3;
+                f1 = f1*w+4.371064420753005429514E-5;
+                f1 = f1*w+2.141783679522602903795E-6;
+                f2 = 1.000000000000000000000;
+                f2 = f2*w+8.585231423622028380768E-1;
+                f2 = f2*w+4.483285822873995129957E-1;
+                f2 = f2*w+7.687932158124475434091E-2;
+                f2 = f2*w+2.449868241021887685904E-2;
+                f2 = f2*w+8.832165941927796567926E-4;
+                f2 = f2*w+4.590952299511353531215E-4;
+                f2 = f2*w+-4.729848351866523044863E-6;
+                f2 = f2*w+2.665195537390710170105E-6;
+                f = f1/f2;
+                result = Math.Exp(x)*w*(1+w*f);
+                return result;
+            }
+            if( (double)(x)<(double)(16) )
+            {
+                w = 1/x;
+                f1 = -2.106934601691916512584;
+                f1 = f1*w+1.732733869664688041885;
+                f1 = f1*w-2.423619178935841904839E-1;
+                f1 = f1*w+2.322724180937565842585E-2;
+                f1 = f1*w+2.372880440493179832059E-4;
+                f1 = f1*w-8.343219561192552752335E-5;
+                f1 = f1*w+1.363408795605250394881E-5;
+                f1 = f1*w-3.655412321999253963714E-7;
+                f1 = f1*w+1.464941733975961318456E-8;
+                f1 = f1*w+6.176407863710360207074E-10;
+                f2 = 1.000000000000000000000;
+                f2 = f2*w-2.298062239901678075778E-1;
+                f2 = f2*w+1.105077041474037862347E-1;
+                f2 = f2*w-1.566542966630792353556E-2;
+                f2 = f2*w+2.761106850817352773874E-3;
+                f2 = f2*w-2.089148012284048449115E-4;
+                f2 = f2*w+1.708528938807675304186E-5;
+                f2 = f2*w-4.459311796356686423199E-7;
+                f2 = f2*w+1.394634930353847498145E-8;
+                f2 = f2*w+6.150865933977338354138E-10;
+                f = f1/f2;
+                result = Math.Exp(x)*w*(1+w*f);
+                return result;
+            }
+            if( (double)(x)<(double)(32) )
+            {
+                w = 1/x;
+                f1 = -2.458119367674020323359E-1;
+                f1 = f1*w-1.483382253322077687183E-1;
+                f1 = f1*w+7.248291795735551591813E-2;
+                f1 = f1*w-1.348315687380940523823E-2;
+                f1 = f1*w+1.342775069788636972294E-3;
+                f1 = f1*w-7.942465637159712264564E-5;
+                f1 = f1*w+2.644179518984235952241E-6;
+                f1 = f1*w-4.239473659313765177195E-8;
+                f2 = 1.000000000000000000000;
+                f2 = f2*w-1.044225908443871106315E-1;
+                f2 = f2*w-2.676453128101402655055E-1;
+                f2 = f2*w+9.695000254621984627876E-2;
+                f2 = f2*w-1.601745692712991078208E-2;
+                f2 = f2*w+1.496414899205908021882E-3;
+                f2 = f2*w-8.462452563778485013756E-5;
+                f2 = f2*w+2.728938403476726394024E-6;
+                f2 = f2*w-4.239462431819542051337E-8;
+                f = f1/f2;
+                result = Math.Exp(x)*w*(1+w*f);
+                return result;
+            }
+            if( (double)(x)<(double)(64) )
+            {
+                w = 1/x;
+                f1 = 1.212561118105456670844E-1;
+                f1 = f1*w-5.823133179043894485122E-1;
+                f1 = f1*w+2.348887314557016779211E-1;
+                f1 = f1*w-3.040034318113248237280E-2;
+                f1 = f1*w+1.510082146865190661777E-3;
+                f1 = f1*w-2.523137095499571377122E-5;
+                f2 = 1.000000000000000000000;
+                f2 = f2*w-1.002252150365854016662;
+                f2 = f2*w+2.928709694872224144953E-1;
+                f2 = f2*w-3.337004338674007801307E-2;
+                f2 = f2*w+1.560544881127388842819E-3;
+                f2 = f2*w-2.523137093603234562648E-5;
+                f = f1/f2;
+                result = Math.Exp(x)*w*(1+w*f);
+                return result;
+            }
+            w = 1/x;
+            f1 = -7.657847078286127362028E-1;
+            f1 = f1*w+6.886192415566705051750E-1;
+            f1 = f1*w-2.132598113545206124553E-1;
+            f1 = f1*w+3.346107552384193813594E-2;
+            f1 = f1*w-3.076541477344756050249E-3;
+            f1 = f1*w+1.747119316454907477380E-4;
+            f1 = f1*w-6.103711682274170530369E-6;
+            f1 = f1*w+1.218032765428652199087E-7;
+            f1 = f1*w-1.086076102793290233007E-9;
+            f2 = 1.000000000000000000000;
+            f2 = f2*w-1.888802868662308731041;
+            f2 = f2*w+1.066691687211408896850;
+            f2 = f2*w-2.751915982306380647738E-1;
+            f2 = f2*w+3.930852688233823569726E-2;
+            f2 = f2*w-3.414684558602365085394E-3;
+            f2 = f2*w+1.866844370703555398195E-4;
+            f2 = f2*w-6.345146083130515357861E-6;
+            f2 = f2*w+1.239754287483206878024E-7;
+            f2 = f2*w-1.086076102793126632978E-9;
+            f = f1/f2;
+            result = Math.Exp(x)*w*(1+w*f);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Exponential integral En(x)
+
+        Evaluates the exponential integral
+
+                        inf.
+                          -
+                         | |   -xt
+                         |    e
+             E (x)  =    |    ----  dt.
+              n          |      n
+                       | |     t
+                        -
+                         1
+
+
+        Both n and x must be nonnegative.
+
+        The routine employs either a power series, a continued
+        fraction, or an asymptotic formula depending on the
+        relative values of n and x.
+
+        ACCURACY:
+
+                             Relative error:
+        arithmetic   domain     # trials      peak         rms
+           IEEE      0, 30       10000       1.7e-15     3.6e-16
+
+        Cephes Math Library Release 2.8:  June, 2000
+        Copyright 1985, 2000 by Stephen L. Moshier
+        *************************************************************************/
+        public static double exponentialintegralen(double x,
+            int n)
+        {
+            double result = 0;
+            double r = 0;
+            double t = 0;
+            double yk = 0;
+            double xk = 0;
+            double pk = 0;
+            double pkm1 = 0;
+            double pkm2 = 0;
+            double qk = 0;
+            double qkm1 = 0;
+            double qkm2 = 0;
+            double psi = 0;
+            double z = 0;
+            int i = 0;
+            int k = 0;
+            double big = 0;
+            double eul = 0;
+
+            eul = 0.57721566490153286060;
+            big = 1.44115188075855872*Math.Pow(10, 17);
+            if( ((n<0 || (double)(x)<(double)(0)) || (double)(x)>(double)(170)) || ((double)(x)==(double)(0) && n<2) )
+            {
+                result = -1;
+                return result;
+            }
+            if( (double)(x)==(double)(0) )
+            {
+                result = (double)1/(double)(n-1);
+                return result;
+            }
+            if( n==0 )
+            {
+                result = Math.Exp(-x)/x;
+                return result;
+            }
+            if( n>5000 )
+            {
+                xk = x+n;
+                yk = 1/(xk*xk);
+                t = n;
+                result = yk*t*(6*x*x-8*t*x+t*t);
+                result = yk*(result+t*(t-2.0*x));
+                result = yk*(result+t);
+                result = (result+1)*Math.Exp(-x)/xk;
+                return result;
+            }
+            if( (double)(x)<=(double)(1) )
+            {
+                psi = -eul-Math.Log(x);
+                for(i=1; i<=n-1; i++)
+                {
+                    psi = psi+(double)1/(double)i;
+                }
+                z = -x;
+                xk = 0;
+                yk = 1;
+                pk = 1-n;
+                if( n==1 )
+                {
+                    result = 0.0;
+                }
+                else
+                {
+                    result = 1.0/pk;
+                }
+                do
+                {
+                    xk = xk+1;
+                    yk = yk*z/xk;
+                    pk = pk+1;
+                    if( (double)(pk)!=(double)(0) )
+                    {
+                        result = result+yk/pk;
+                    }
+                    if( (double)(result)!=(double)(0) )
+                    {
+                        t = Math.Abs(yk/result);
+                    }
+                    else
+                    {
+                        t = 1;
+                    }
+                }
+                while( (double)(t)>=(double)(math.machineepsilon) );
+                t = 1;
+                for(i=1; i<=n-1; i++)
+                {
+                    t = t*z/i;
+                }
+                result = psi*t-result;
+                return result;
+            }
+            else
+            {
+                k = 1;
+                pkm2 = 1;
+                qkm2 = x;
+                pkm1 = 1.0;
+                qkm1 = x+n;
+                result = pkm1/qkm1;
+                do
+                {
+                    k = k+1;
+                    if( k%2==1 )
+                    {
+                        yk = 1;
+                        xk = n+(double)(k-1)/(double)2;
+                    }
+                    else
+                    {
+                        yk = x;
+                        xk = (double)k/(double)2;
+                    }
+                    pk = pkm1*yk+pkm2*xk;
+                    qk = qkm1*yk+qkm2*xk;
+                    if( (double)(qk)!=(double)(0) )
+                    {
+                        r = pk/qk;
+                        t = Math.Abs((result-r)/r);
+                        result = r;
+                    }
+                    else
+                    {
+                        t = 1;
+                    }
+                    pkm2 = pkm1;
+                    pkm1 = pk;
+                    qkm2 = qkm1;
+                    qkm1 = qk;
+                    if( (double)(Math.Abs(pk))>(double)(big) )
+                    {
+                        pkm2 = pkm2/big;
+                        pkm1 = pkm1/big;
+                        qkm2 = qkm2/big;
+                        qkm1 = qkm1/big;
+                    }
+                }
+                while( (double)(t)>=(double)(math.machineepsilon) );
+                result = result*Math.Exp(-x);
+            }
+            return result;
+        }
+
+
+    }
+    public class fdistr
+    {
+        /*************************************************************************
+        F distribution
+
+        Returns the area from zero to x under the F density
+        function (also known as Snedcor's density or the
+        variance ratio density).  This is the density
+        of x = (u1/df1)/(u2/df2), where u1 and u2 are random
+        variables having Chi square distributions with df1
+        and df2 degrees of freedom, respectively.
+        The incomplete beta integral is used, according to the
+        formula
+
+        P(x) = incbet( df1/2, df2/2, (df1*x/(df2 + df1*x) ).
+
+
+        The arguments a and b are greater than zero, and x is
+        nonnegative.
+
+        ACCURACY:
+
+        Tested at random points (a,b,x).
+
+                       x     a,b                     Relative error:
+        arithmetic  domain  domain     # trials      peak         rms
+           IEEE      0,1    0,100       100000      9.8e-15     1.7e-15
+           IEEE      1,5    0,100       100000      6.5e-15     3.5e-16
+           IEEE      0,1    1,10000     100000      2.2e-11     3.3e-12
+           IEEE      1,5    1,10000     100000      1.1e-11     1.7e-13
+
+        Cephes Math Library Release 2.8:  June, 2000
+        Copyright 1984, 1987, 1995, 2000 by Stephen L. Moshier
+        *************************************************************************/
+        public static double fdistribution(int a,
+            int b,
+            double x)
+        {
+            double result = 0;
+            double w = 0;
+
+            alglib.ap.assert((a>=1 && b>=1) && (double)(x)>=(double)(0), "Domain error in FDistribution");
+            w = a*x;
+            w = w/(b+w);
+            result = ibetaf.incompletebeta(0.5*a, 0.5*b, w);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Complemented F distribution
+
+        Returns the area from x to infinity under the F density
+        function (also known as Snedcor's density or the
+        variance ratio density).
+
+
+                             inf.
+                              -
+                     1       | |  a-1      b-1
+        1-P(x)  =  ------    |   t    (1-t)    dt
+                   B(a,b)  | |
+                            -
+                             x
+
+
+        The incomplete beta integral is used, according to the
+        formula
+
+        P(x) = incbet( df2/2, df1/2, (df2/(df2 + df1*x) ).
+
+
+        ACCURACY:
+
+        Tested at random points (a,b,x) in the indicated intervals.
+                       x     a,b                     Relative error:
+        arithmetic  domain  domain     # trials      peak         rms
+           IEEE      0,1    1,100       100000      3.7e-14     5.9e-16
+           IEEE      1,5    1,100       100000      8.0e-15     1.6e-15
+           IEEE      0,1    1,10000     100000      1.8e-11     3.5e-13
+           IEEE      1,5    1,10000     100000      2.0e-11     3.0e-12
+
+        Cephes Math Library Release 2.8:  June, 2000
+        Copyright 1984, 1987, 1995, 2000 by Stephen L. Moshier
+        *************************************************************************/
+        public static double fcdistribution(int a,
+            int b,
+            double x)
+        {
+            double result = 0;
+            double w = 0;
+
+            alglib.ap.assert((a>=1 && b>=1) && (double)(x)>=(double)(0), "Domain error in FCDistribution");
+            w = b/(b+a*x);
+            result = ibetaf.incompletebeta(0.5*b, 0.5*a, w);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Inverse of complemented F distribution
+
+        Finds the F density argument x such that the integral
+        from x to infinity of the F density is equal to the
+        given probability p.
+
+        This is accomplished using the inverse beta integral
+        function and the relations
+
+             z = incbi( df2/2, df1/2, p )
+             x = df2 (1-z) / (df1 z).
+
+        Note: the following relations hold for the inverse of
+        the uncomplemented F distribution:
+
+             z = incbi( df1/2, df2/2, p )
+             x = df2 z / (df1 (1-z)).
+
+        ACCURACY:
+
+        Tested at random points (a,b,p).
+
+                     a,b                     Relative error:
+        arithmetic  domain     # trials      peak         rms
+         For p between .001 and 1:
+           IEEE     1,100       100000      8.3e-15     4.7e-16
+           IEEE     1,10000     100000      2.1e-11     1.4e-13
+         For p between 10^-6 and 10^-3:
+           IEEE     1,100        50000      1.3e-12     8.4e-15
+           IEEE     1,10000      50000      3.0e-12     4.8e-14
+
+        Cephes Math Library Release 2.8:  June, 2000
+        Copyright 1984, 1987, 1995, 2000 by Stephen L. Moshier
+        *************************************************************************/
+        public static double invfdistribution(int a,
+            int b,
+            double y)
+        {
+            double result = 0;
+            double w = 0;
+
+            alglib.ap.assert(((a>=1 && b>=1) && (double)(y)>(double)(0)) && (double)(y)<=(double)(1), "Domain error in InvFDistribution");
+            
+            //
+            // Compute probability for x = 0.5
+            //
+            w = ibetaf.incompletebeta(0.5*b, 0.5*a, 0.5);
+            
+            //
+            // If that is greater than y, then the solution w < .5
+            // Otherwise, solve at 1-y to remove cancellation in (b - b*w)
+            //
+            if( (double)(w)>(double)(y) || (double)(y)<(double)(0.001) )
+            {
+                w = ibetaf.invincompletebeta(0.5*b, 0.5*a, y);
+                result = (b-b*w)/(a*w);
+            }
+            else
+            {
+                w = ibetaf.invincompletebeta(0.5*a, 0.5*b, 1.0-y);
+                result = b*w/(a*(1.0-w));
+            }
+            return result;
+        }
+
+
+    }
+    public class fresnel
+    {
+        /*************************************************************************
+        Fresnel integral
+
+        Evaluates the Fresnel integrals
+
+                  x
+                  -
+                 | |
+        C(x) =   |   cos(pi/2 t**2) dt,
+               | |
+                -
+                 0
+
+                  x
+                  -
+                 | |
+        S(x) =   |   sin(pi/2 t**2) dt.
+               | |
+                -
+                 0
+
+
+        The integrals are evaluated by a power series for x < 1.
+        For x >= 1 auxiliary functions f(x) and g(x) are employed
+        such that
+
+        C(x) = 0.5 + f(x) sin( pi/2 x**2 ) - g(x) cos( pi/2 x**2 )
+        S(x) = 0.5 - f(x) cos( pi/2 x**2 ) - g(x) sin( pi/2 x**2 )
+
+
+
+        ACCURACY:
+
+         Relative error.
+
+        Arithmetic  function   domain     # trials      peak         rms
+          IEEE       S(x)      0, 10       10000       2.0e-15     3.2e-16
+          IEEE       C(x)      0, 10       10000       1.8e-15     3.3e-16
+
+        Cephes Math Library Release 2.8:  June, 2000
+        Copyright 1984, 1987, 1989, 2000 by Stephen L. Moshier
+        *************************************************************************/
+        public static void fresnelintegral(double x,
+            ref double c,
+            ref double s)
+        {
+            double xxa = 0;
+            double f = 0;
+            double g = 0;
+            double cc = 0;
+            double ss = 0;
+            double t = 0;
+            double u = 0;
+            double x2 = 0;
+            double sn = 0;
+            double sd = 0;
+            double cn = 0;
+            double cd = 0;
+            double fn = 0;
+            double fd = 0;
+            double gn = 0;
+            double gd = 0;
+            double mpi = 0;
+            double mpio2 = 0;
+
+            mpi = 3.14159265358979323846;
+            mpio2 = 1.57079632679489661923;
+            xxa = x;
+            x = Math.Abs(xxa);
+            x2 = x*x;
+            if( (double)(x2)<(double)(2.5625) )
+            {
+                t = x2*x2;
+                sn = -2.99181919401019853726E3;
+                sn = sn*t+7.08840045257738576863E5;
+                sn = sn*t-6.29741486205862506537E7;
+                sn = sn*t+2.54890880573376359104E9;
+                sn = sn*t-4.42979518059697779103E10;
+                sn = sn*t+3.18016297876567817986E11;
+                sd = 1.00000000000000000000E0;
+                sd = sd*t+2.81376268889994315696E2;
+                sd = sd*t+4.55847810806532581675E4;
+                sd = sd*t+5.17343888770096400730E6;
+                sd = sd*t+4.19320245898111231129E8;
+                sd = sd*t+2.24411795645340920940E10;
+                sd = sd*t+6.07366389490084639049E11;
+                cn = -4.98843114573573548651E-8;
+                cn = cn*t+9.50428062829859605134E-6;
+                cn = cn*t-6.45191435683965050962E-4;
+                cn = cn*t+1.88843319396703850064E-2;
+                cn = cn*t-2.05525900955013891793E-1;
+                cn = cn*t+9.99999999999999998822E-1;
+                cd = 3.99982968972495980367E-12;
+                cd = cd*t+9.15439215774657478799E-10;
+                cd = cd*t+1.25001862479598821474E-7;
+                cd = cd*t+1.22262789024179030997E-5;
+                cd = cd*t+8.68029542941784300606E-4;
+                cd = cd*t+4.12142090722199792936E-2;
+                cd = cd*t+1.00000000000000000118E0;
+                s = Math.Sign(xxa)*x*x2*sn/sd;
+                c = Math.Sign(xxa)*x*cn/cd;
+                return;
+            }
+            if( (double)(x)>(double)(36974.0) )
+            {
+                c = Math.Sign(xxa)*0.5;
+                s = Math.Sign(xxa)*0.5;
+                return;
+            }
+            x2 = x*x;
+            t = mpi*x2;
+            u = 1/(t*t);
+            t = 1/t;
+            fn = 4.21543555043677546506E-1;
+            fn = fn*u+1.43407919780758885261E-1;
+            fn = fn*u+1.15220955073585758835E-2;
+            fn = fn*u+3.45017939782574027900E-4;
+            fn = fn*u+4.63613749287867322088E-6;
+            fn = fn*u+3.05568983790257605827E-8;
+            fn = fn*u+1.02304514164907233465E-10;
+            fn = fn*u+1.72010743268161828879E-13;
+            fn = fn*u+1.34283276233062758925E-16;
+            fn = fn*u+3.76329711269987889006E-20;
+            fd = 1.00000000000000000000E0;
+            fd = fd*u+7.51586398353378947175E-1;
+            fd = fd*u+1.16888925859191382142E-1;
+            fd = fd*u+6.44051526508858611005E-3;
+            fd = fd*u+1.55934409164153020873E-4;
+            fd = fd*u+1.84627567348930545870E-6;
+            fd = fd*u+1.12699224763999035261E-8;
+            fd = fd*u+3.60140029589371370404E-11;
+            fd = fd*u+5.88754533621578410010E-14;
+            fd = fd*u+4.52001434074129701496E-17;
+            fd = fd*u+1.25443237090011264384E-20;
+            gn = 5.04442073643383265887E-1;
+            gn = gn*u+1.97102833525523411709E-1;
+            gn = gn*u+1.87648584092575249293E-2;
+            gn = gn*u+6.84079380915393090172E-4;
+            gn = gn*u+1.15138826111884280931E-5;
+            gn = gn*u+9.82852443688422223854E-8;
+            gn = gn*u+4.45344415861750144738E-10;
+            gn = gn*u+1.08268041139020870318E-12;
+            gn = gn*u+1.37555460633261799868E-15;
+            gn = gn*u+8.36354435630677421531E-19;
+            gn = gn*u+1.86958710162783235106E-22;
+            gd = 1.00000000000000000000E0;
+            gd = gd*u+1.47495759925128324529E0;
+            gd = gd*u+3.37748989120019970451E-1;
+            gd = gd*u+2.53603741420338795122E-2;
+            gd = gd*u+8.14679107184306179049E-4;
+            gd = gd*u+1.27545075667729118702E-5;
+            gd = gd*u+1.04314589657571990585E-7;
+            gd = gd*u+4.60680728146520428211E-10;
+            gd = gd*u+1.10273215066240270757E-12;
+            gd = gd*u+1.38796531259578871258E-15;
+            gd = gd*u+8.39158816283118707363E-19;
+            gd = gd*u+1.86958710162783236342E-22;
+            f = 1-u*fn/fd;
+            g = t*gn/gd;
+            t = mpio2*x2;
+            cc = Math.Cos(t);
+            ss = Math.Sin(t);
+            t = mpi*x;
+            c = 0.5+(f*ss-g*cc)/t;
+            s = 0.5-(f*cc+g*ss)/t;
+            c = c*Math.Sign(xxa);
+            s = s*Math.Sign(xxa);
+        }
+
+
+    }
+    public class hermite
+    {
+        /*************************************************************************
+        Calculation of the value of the Hermite polynomial.
+
+        Parameters:
+            n   -   degree, n>=0
+            x   -   argument
+
+        Result:
+            the value of the Hermite polynomial Hn at x
+        *************************************************************************/
+        public static double hermitecalculate(int n,
+            double x)
+        {
+            double result = 0;
+            int i = 0;
+            double a = 0;
+            double b = 0;
+
+            result = 0;
+            
+            //
+            // Prepare A and B
+            //
+            a = 1;
+            b = 2*x;
+            
+            //
+            // Special cases: N=0 or N=1
+            //
+            if( n==0 )
+            {
+                result = a;
+                return result;
+            }
+            if( n==1 )
+            {
+                result = b;
+                return result;
+            }
+            
+            //
+            // General case: N>=2
+            //
+            for(i=2; i<=n; i++)
+            {
+                result = 2*x*b-2*(i-1)*a;
+                a = b;
+                b = result;
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        Summation of Hermite polynomials using Clenshaws recurrence formula.
+
+        This routine calculates
+            c[0]*H0(x) + c[1]*H1(x) + ... + c[N]*HN(x)
+
+        Parameters:
+            n   -   degree, n>=0
+            x   -   argument
+
+        Result:
+            the value of the Hermite polynomial at x
+        *************************************************************************/
+        public static double hermitesum(double[] c,
+            int n,
+            double x)
+        {
+            double result = 0;
+            double b1 = 0;
+            double b2 = 0;
+            int i = 0;
+
+            b1 = 0;
+            b2 = 0;
+            result = 0;
+            for(i=n; i>=0; i--)
+            {
+                result = 2*(x*b1-(i+1)*b2)+c[i];
+                b2 = b1;
+                b1 = result;
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        Representation of Hn as C[0] + C[1]*X + ... + C[N]*X^N
+
+        Input parameters:
+            N   -   polynomial degree, n>=0
+
+        Output parameters:
+            C   -   coefficients
+        *************************************************************************/
+        public static void hermitecoefficients(int n,
+            ref double[] c)
+        {
+            int i = 0;
+
+            c = new double[0];
+
+            c = new double[n+1];
+            for(i=0; i<=n; i++)
+            {
+                c[i] = 0;
+            }
+            c[n] = Math.Exp(n*Math.Log(2));
+            for(i=0; i<=n/2-1; i++)
+            {
+                c[n-2*(i+1)] = -(c[n-2*i]*(n-2*i)*(n-2*i-1)/4/(i+1));
+            }
+        }
+
+
+    }
+    public class jacobianelliptic
+    {
+        /*************************************************************************
+        Jacobian Elliptic Functions
+
+        Evaluates the Jacobian elliptic functions sn(u|m), cn(u|m),
+        and dn(u|m) of parameter m between 0 and 1, and real
+        argument u.
+
+        These functions are periodic, with quarter-period on the
+        real axis equal to the complete elliptic integral
+        ellpk(1.0-m).
+
+        Relation to incomplete elliptic integral:
+        If u = ellik(phi,m), then sn(u|m) = sin(phi),
+        and cn(u|m) = cos(phi).  Phi is called the amplitude of u.
+
+        Computation is by means of the arithmetic-geometric mean
+        algorithm, except when m is within 1e-9 of 0 or 1.  In the
+        latter case with m close to 1, the approximation applies
+        only for phi < pi/2.
+
+        ACCURACY:
+
+        Tested at random points with u between 0 and 10, m between
+        0 and 1.
+
+                   Absolute error (* = relative error):
+        arithmetic   function   # trials      peak         rms
+           IEEE      phi         10000       9.2e-16*    1.4e-16*
+           IEEE      sn          50000       4.1e-15     4.6e-16
+           IEEE      cn          40000       3.6e-15     4.4e-16
+           IEEE      dn          10000       1.3e-12     1.8e-14
+
+         Peak error observed in consistency check using addition
+        theorem for sn(u+v) was 4e-16 (absolute).  Also tested by
+        the above relation to the incomplete elliptic integral.
+        Accuracy deteriorates when u is large.
+
+        Cephes Math Library Release 2.8:  June, 2000
+        Copyright 1984, 1987, 2000 by Stephen L. Moshier
+        *************************************************************************/
+        public static void jacobianellipticfunctions(double u,
+            double m,
+            ref double sn,
+            ref double cn,
+            ref double dn,
+            ref double ph)
+        {
+            double ai = 0;
+            double b = 0;
+            double phi = 0;
+            double t = 0;
+            double twon = 0;
+            double[] a = new double[0];
+            double[] c = new double[0];
+            int i = 0;
+
+            sn = 0;
+            cn = 0;
+            dn = 0;
+            ph = 0;
+
+            alglib.ap.assert((double)(m)>=(double)(0) && (double)(m)<=(double)(1), "Domain error in JacobianEllipticFunctions: m<0 or m>1");
+            a = new double[8+1];
+            c = new double[8+1];
+            if( (double)(m)<(double)(1.0e-9) )
+            {
+                t = Math.Sin(u);
+                b = Math.Cos(u);
+                ai = 0.25*m*(u-t*b);
+                sn = t-ai*b;
+                cn = b+ai*t;
+                ph = u-ai;
+                dn = 1.0-0.5*m*t*t;
+                return;
+            }
+            if( (double)(m)>=(double)(0.9999999999) )
+            {
+                ai = 0.25*(1.0-m);
+                b = Math.Cosh(u);
+                t = Math.Tanh(u);
+                phi = 1.0/b;
+                twon = b*Math.Sinh(u);
+                sn = t+ai*(twon-u)/(b*b);
+                ph = 2.0*Math.Atan(Math.Exp(u))-1.57079632679489661923+ai*(twon-u)/b;
+                ai = ai*t*phi;
+                cn = phi-ai*(twon-u);
+                dn = phi+ai*(twon+u);
+                return;
+            }
+            a[0] = 1.0;
+            b = Math.Sqrt(1.0-m);
+            c[0] = Math.Sqrt(m);
+            twon = 1.0;
+            i = 0;
+            while( (double)(Math.Abs(c[i]/a[i]))>(double)(math.machineepsilon) )
+            {
+                if( i>7 )
+                {
+                    alglib.ap.assert(false, "Overflow in JacobianEllipticFunctions");
+                    break;
+                }
+                ai = a[i];
+                i = i+1;
+                c[i] = 0.5*(ai-b);
+                t = Math.Sqrt(ai*b);
+                a[i] = 0.5*(ai+b);
+                b = t;
+                twon = twon*2.0;
+            }
+            phi = twon*a[i]*u;
+            do
+            {
+                t = c[i]*Math.Sin(phi)/a[i];
+                b = phi;
+                phi = (Math.Asin(t)+phi)/2.0;
+                i = i-1;
+            }
+            while( i!=0 );
+            sn = Math.Sin(phi);
+            t = Math.Cos(phi);
+            cn = t;
+            dn = t/Math.Cos(phi-b);
+            ph = phi;
+        }
+
+
+    }
+    public class laguerre
+    {
+        /*************************************************************************
+        Calculation of the value of the Laguerre polynomial.
+
+        Parameters:
+            n   -   degree, n>=0
+            x   -   argument
+
+        Result:
+            the value of the Laguerre polynomial Ln at x
+        *************************************************************************/
+        public static double laguerrecalculate(int n,
+            double x)
+        {
+            double result = 0;
+            double a = 0;
+            double b = 0;
+            double i = 0;
+
+            result = 1;
+            a = 1;
+            b = 1-x;
+            if( n==1 )
+            {
+                result = b;
+            }
+            i = 2;
+            while( (double)(i)<=(double)(n) )
+            {
+                result = ((2*i-1-x)*b-(i-1)*a)/i;
+                a = b;
+                b = result;
+                i = i+1;
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        Summation of Laguerre polynomials using Clenshaws recurrence formula.
+
+        This routine calculates c[0]*L0(x) + c[1]*L1(x) + ... + c[N]*LN(x)
+
+        Parameters:
+            n   -   degree, n>=0
+            x   -   argument
+
+        Result:
+            the value of the Laguerre polynomial at x
+        *************************************************************************/
+        public static double laguerresum(double[] c,
+            int n,
+            double x)
+        {
+            double result = 0;
+            double b1 = 0;
+            double b2 = 0;
+            int i = 0;
+
+            b1 = 0;
+            b2 = 0;
+            result = 0;
+            for(i=n; i>=0; i--)
+            {
+                result = (2*i+1-x)*b1/(i+1)-(i+1)*b2/(i+2)+c[i];
+                b2 = b1;
+                b1 = result;
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        Representation of Ln as C[0] + C[1]*X + ... + C[N]*X^N
+
+        Input parameters:
+            N   -   polynomial degree, n>=0
+
+        Output parameters:
+            C   -   coefficients
+        *************************************************************************/
+        public static void laguerrecoefficients(int n,
+            ref double[] c)
+        {
+            int i = 0;
+
+            c = new double[0];
+
+            c = new double[n+1];
+            c[0] = 1;
+            for(i=0; i<=n-1; i++)
+            {
+                c[i+1] = -(c[i]*(n-i)/(i+1)/(i+1));
+            }
+        }
+
+
+    }
+    public class legendre
+    {
+        /*************************************************************************
+        Calculation of the value of the Legendre polynomial Pn.
+
+        Parameters:
+            n   -   degree, n>=0
+            x   -   argument
+
+        Result:
+            the value of the Legendre polynomial Pn at x
+        *************************************************************************/
+        public static double legendrecalculate(int n,
+            double x)
+        {
+            double result = 0;
+            double a = 0;
+            double b = 0;
+            int i = 0;
+
+            result = 1;
+            a = 1;
+            b = x;
+            if( n==0 )
+            {
+                result = a;
+                return result;
+            }
+            if( n==1 )
+            {
+                result = b;
+                return result;
+            }
+            for(i=2; i<=n; i++)
+            {
+                result = ((2*i-1)*x*b-(i-1)*a)/i;
+                a = b;
+                b = result;
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        Summation of Legendre polynomials using Clenshaws recurrence formula.
+
+        This routine calculates
+            c[0]*P0(x) + c[1]*P1(x) + ... + c[N]*PN(x)
+
+        Parameters:
+            n   -   degree, n>=0
+            x   -   argument
+
+        Result:
+            the value of the Legendre polynomial at x
+        *************************************************************************/
+        public static double legendresum(double[] c,
+            int n,
+            double x)
+        {
+            double result = 0;
+            double b1 = 0;
+            double b2 = 0;
+            int i = 0;
+
+            b1 = 0;
+            b2 = 0;
+            result = 0;
+            for(i=n; i>=0; i--)
+            {
+                result = (2*i+1)*x*b1/(i+1)-(i+1)*b2/(i+2)+c[i];
+                b2 = b1;
+                b1 = result;
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        Representation of Pn as C[0] + C[1]*X + ... + C[N]*X^N
+
+        Input parameters:
+            N   -   polynomial degree, n>=0
+
+        Output parameters:
+            C   -   coefficients
+        *************************************************************************/
+        public static void legendrecoefficients(int n,
+            ref double[] c)
+        {
+            int i = 0;
+
+            c = new double[0];
+
+            c = new double[n+1];
+            for(i=0; i<=n; i++)
+            {
+                c[i] = 0;
+            }
+            c[n] = 1;
+            for(i=1; i<=n; i++)
+            {
+                c[n] = c[n]*(n+i)/2/i;
+            }
+            for(i=0; i<=n/2-1; i++)
+            {
+                c[n-2*(i+1)] = -(c[n-2*i]*(n-2*i)*(n-2*i-1)/2/(i+1)/(2*(n-i)-1));
+            }
+        }
+
+
+    }
+    public class poissondistr
+    {
+        /*************************************************************************
+        Poisson distribution
+
+        Returns the sum of the first k+1 terms of the Poisson
+        distribution:
+
+          k         j
+          --   -m  m
+          >   e    --
+          --       j!
+         j=0
+
+        The terms are not summed directly; instead the incomplete
+        gamma integral is employed, according to the relation
+
+        y = pdtr( k, m ) = igamc( k+1, m ).
+
+        The arguments must both be positive.
+        ACCURACY:
+
+        See incomplete gamma function
+
+        Cephes Math Library Release 2.8:  June, 2000
+        Copyright 1984, 1987, 1995, 2000 by Stephen L. Moshier
+        *************************************************************************/
+        public static double poissondistribution(int k,
+            double m)
+        {
+            double result = 0;
+
+            alglib.ap.assert(k>=0 && (double)(m)>(double)(0), "Domain error in PoissonDistribution");
+            result = igammaf.incompletegammac(k+1, m);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Complemented Poisson distribution
+
+        Returns the sum of the terms k+1 to infinity of the Poisson
+        distribution:
+
+         inf.       j
+          --   -m  m
+          >   e    --
+          --       j!
+         j=k+1
+
+        The terms are not summed directly; instead the incomplete
+        gamma integral is employed, according to the formula
+
+        y = pdtrc( k, m ) = igam( k+1, m ).
+
+        The arguments must both be positive.
+
+        ACCURACY:
+
+        See incomplete gamma function
+
+        Cephes Math Library Release 2.8:  June, 2000
+        Copyright 1984, 1987, 1995, 2000 by Stephen L. Moshier
+        *************************************************************************/
+        public static double poissoncdistribution(int k,
+            double m)
+        {
+            double result = 0;
+
+            alglib.ap.assert(k>=0 && (double)(m)>(double)(0), "Domain error in PoissonDistributionC");
+            result = igammaf.incompletegamma(k+1, m);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Inverse Poisson distribution
+
+        Finds the Poisson variable x such that the integral
+        from 0 to x of the Poisson density is equal to the
+        given probability y.
+
+        This is accomplished using the inverse gamma integral
+        function and the relation
+
+           m = igami( k+1, y ).
+
+        ACCURACY:
+
+        See inverse incomplete gamma function
+
+        Cephes Math Library Release 2.8:  June, 2000
+        Copyright 1984, 1987, 1995, 2000 by Stephen L. Moshier
+        *************************************************************************/
+        public static double invpoissondistribution(int k,
+            double y)
+        {
+            double result = 0;
+
+            alglib.ap.assert((k>=0 && (double)(y)>=(double)(0)) && (double)(y)<(double)(1), "Domain error in InvPoissonDistribution");
+            result = igammaf.invincompletegammac(k+1, y);
+            return result;
+        }
+
+
+    }
+    public class psif
+    {
+        /*************************************************************************
+        Psi (digamma) function
+
+                     d      -
+          psi(x)  =  -- ln | (x)
+                     dx
+
+        is the logarithmic derivative of the gamma function.
+        For integer x,
+                          n-1
+                           -
+        psi(n) = -EUL  +   >  1/k.
+                           -
+                          k=1
+
+        This formula is used for 0 < n <= 10.  If x is negative, it
+        is transformed to a positive argument by the reflection
+        formula  psi(1-x) = psi(x) + pi cot(pi x).
+        For general positive x, the argument is made greater than 10
+        using the recurrence  psi(x+1) = psi(x) + 1/x.
+        Then the following asymptotic expansion is applied:
+
+                                  inf.   B
+                                   -      2k
+        psi(x) = log(x) - 1/2x -   >   -------
+                                   -        2k
+                                  k=1   2k x
+
+        where the B2k are Bernoulli numbers.
+
+        ACCURACY:
+           Relative error (except absolute when |psi| < 1):
+        arithmetic   domain     # trials      peak         rms
+           IEEE      0,30        30000       1.3e-15     1.4e-16
+           IEEE      -30,0       40000       1.5e-15     2.2e-16
+
+        Cephes Math Library Release 2.8:  June, 2000
+        Copyright 1984, 1987, 1992, 2000 by Stephen L. Moshier
+        *************************************************************************/
+        public static double psi(double x)
+        {
+            double result = 0;
+            double p = 0;
+            double q = 0;
+            double nz = 0;
+            double s = 0;
+            double w = 0;
+            double y = 0;
+            double z = 0;
+            double polv = 0;
+            int i = 0;
+            int n = 0;
+            int negative = 0;
+
+            negative = 0;
+            nz = 0.0;
+            if( (double)(x)<=(double)(0) )
+            {
+                negative = 1;
+                q = x;
+                p = (int)Math.Floor(q);
+                if( (double)(p)==(double)(q) )
+                {
+                    alglib.ap.assert(false, "Singularity in Psi(x)");
+                    result = math.maxrealnumber;
+                    return result;
+                }
+                nz = q-p;
+                if( (double)(nz)!=(double)(0.5) )
+                {
+                    if( (double)(nz)>(double)(0.5) )
+                    {
+                        p = p+1.0;
+                        nz = q-p;
+                    }
+                    nz = Math.PI/Math.Tan(Math.PI*nz);
+                }
+                else
+                {
+                    nz = 0.0;
+                }
+                x = 1.0-x;
+            }
+            if( (double)(x)<=(double)(10.0) && (double)(x)==(double)((int)Math.Floor(x)) )
+            {
+                y = 0.0;
+                n = (int)Math.Floor(x);
+                for(i=1; i<=n-1; i++)
+                {
+                    w = i;
+                    y = y+1.0/w;
+                }
+                y = y-0.57721566490153286061;
+            }
+            else
+            {
+                s = x;
+                w = 0.0;
+                while( (double)(s)<(double)(10.0) )
+                {
+                    w = w+1.0/s;
+                    s = s+1.0;
+                }
+                if( (double)(s)<(double)(1.0E17) )
+                {
+                    z = 1.0/(s*s);
+                    polv = 8.33333333333333333333E-2;
+                    polv = polv*z-2.10927960927960927961E-2;
+                    polv = polv*z+7.57575757575757575758E-3;
+                    polv = polv*z-4.16666666666666666667E-3;
+                    polv = polv*z+3.96825396825396825397E-3;
+                    polv = polv*z-8.33333333333333333333E-3;
+                    polv = polv*z+8.33333333333333333333E-2;
+                    y = z*polv;
+                }
+                else
+                {
+                    y = 0.0;
+                }
+                y = Math.Log(s)-0.5/s-y-w;
+            }
+            if( negative!=0 )
+            {
+                y = y-nz;
+            }
+            result = y;
+            return result;
+        }
+
+
+    }
+    public class studenttdistr
+    {
+        /*************************************************************************
+        Student's t distribution
+
+        Computes the integral from minus infinity to t of the Student
+        t distribution with integer k > 0 degrees of freedom:
+
+                                             t
+                                             -
+                                            | |
+                     -                      |         2   -(k+1)/2
+                    | ( (k+1)/2 )           |  (     x   )
+              ----------------------        |  ( 1 + --- )        dx
+                            -               |  (      k  )
+              sqrt( k pi ) | ( k/2 )        |
+                                          | |
+                                           -
+                                          -inf.
+
+        Relation to incomplete beta integral:
+
+               1 - stdtr(k,t) = 0.5 * incbet( k/2, 1/2, z )
+        where
+               z = k/(k + t**2).
+
+        For t < -2, this is the method of computation.  For higher t,
+        a direct method is derived from integration by parts.
+        Since the function is symmetric about t=0, the area under the
+        right tail of the density is found by calling the function
+        with -t instead of t.
+
+        ACCURACY:
+
+        Tested at random 1 <= k <= 25.  The "domain" refers to t.
+                             Relative error:
+        arithmetic   domain     # trials      peak         rms
+           IEEE     -100,-2      50000       5.9e-15     1.4e-15
+           IEEE     -2,100      500000       2.7e-15     4.9e-17
+
+        Cephes Math Library Release 2.8:  June, 2000
+        Copyright 1984, 1987, 1995, 2000 by Stephen L. Moshier
+        *************************************************************************/
+        public static double studenttdistribution(int k,
+            double t)
+        {
+            double result = 0;
+            double x = 0;
+            double rk = 0;
+            double z = 0;
+            double f = 0;
+            double tz = 0;
+            double p = 0;
+            double xsqk = 0;
+            int j = 0;
+
+            alglib.ap.assert(k>0, "Domain error in StudentTDistribution");
+            if( (double)(t)==(double)(0) )
+            {
+                result = 0.5;
+                return result;
+            }
+            if( (double)(t)<(double)(-2.0) )
+            {
+                rk = k;
+                z = rk/(rk+t*t);
+                result = 0.5*ibetaf.incompletebeta(0.5*rk, 0.5, z);
+                return result;
+            }
+            if( (double)(t)<(double)(0) )
+            {
+                x = -t;
+            }
+            else
+            {
+                x = t;
+            }
+            rk = k;
+            z = 1.0+x*x/rk;
+            if( k%2!=0 )
+            {
+                xsqk = x/Math.Sqrt(rk);
+                p = Math.Atan(xsqk);
+                if( k>1 )
+                {
+                    f = 1.0;
+                    tz = 1.0;
+                    j = 3;
+                    while( j<=k-2 && (double)(tz/f)>(double)(math.machineepsilon) )
+                    {
+                        tz = tz*((j-1)/(z*j));
+                        f = f+tz;
+                        j = j+2;
+                    }
+                    p = p+f*xsqk/z;
+                }
+                p = p*2.0/Math.PI;
+            }
+            else
+            {
+                f = 1.0;
+                tz = 1.0;
+                j = 2;
+                while( j<=k-2 && (double)(tz/f)>(double)(math.machineepsilon) )
+                {
+                    tz = tz*((j-1)/(z*j));
+                    f = f+tz;
+                    j = j+2;
+                }
+                p = f*x/Math.Sqrt(z*rk);
+            }
+            if( (double)(t)<(double)(0) )
+            {
+                p = -p;
+            }
+            result = 0.5+0.5*p;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Functional inverse of Student's t distribution
+
+        Given probability p, finds the argument t such that stdtr(k,t)
+        is equal to p.
+
+        ACCURACY:
+
+        Tested at random 1 <= k <= 100.  The "domain" refers to p:
+                             Relative error:
+        arithmetic   domain     # trials      peak         rms
+           IEEE    .001,.999     25000       5.7e-15     8.0e-16
+           IEEE    10^-6,.001    25000       2.0e-12     2.9e-14
+
+        Cephes Math Library Release 2.8:  June, 2000
+        Copyright 1984, 1987, 1995, 2000 by Stephen L. Moshier
+        *************************************************************************/
+        public static double invstudenttdistribution(int k,
+            double p)
+        {
+            double result = 0;
+            double t = 0;
+            double rk = 0;
+            double z = 0;
+            int rflg = 0;
+
+            alglib.ap.assert((k>0 && (double)(p)>(double)(0)) && (double)(p)<(double)(1), "Domain error in InvStudentTDistribution");
+            rk = k;
+            if( (double)(p)>(double)(0.25) && (double)(p)<(double)(0.75) )
+            {
+                if( (double)(p)==(double)(0.5) )
+                {
+                    result = 0;
+                    return result;
+                }
+                z = 1.0-2.0*p;
+                z = ibetaf.invincompletebeta(0.5, 0.5*rk, Math.Abs(z));
+                t = Math.Sqrt(rk*z/(1.0-z));
+                if( (double)(p)<(double)(0.5) )
+                {
+                    t = -t;
+                }
+                result = t;
+                return result;
+            }
+            rflg = -1;
+            if( (double)(p)>=(double)(0.5) )
+            {
+                p = 1.0-p;
+                rflg = 1;
+            }
+            z = ibetaf.invincompletebeta(0.5*rk, 0.5, 2.0*p);
+            if( (double)(math.maxrealnumber*z)<(double)(rk) )
+            {
+                result = rflg*math.maxrealnumber;
+                return result;
+            }
+            t = Math.Sqrt(rk/z-rk);
+            result = rflg*t;
+            return result;
+        }
+
+
+    }
+    public class trigintegrals
+    {
+        /*************************************************************************
+        Sine and cosine integrals
+
+        Evaluates the integrals
+
+                                 x
+                                 -
+                                |  cos t - 1
+          Ci(x) = eul + ln x +  |  --------- dt,
+                                |      t
+                               -
+                                0
+                    x
+                    -
+                   |  sin t
+          Si(x) =  |  ----- dt
+                   |    t
+                  -
+                   0
+
+        where eul = 0.57721566490153286061 is Euler's constant.
+        The integrals are approximated by rational functions.
+        For x > 8 auxiliary functions f(x) and g(x) are employed
+        such that
+
+        Ci(x) = f(x) sin(x) - g(x) cos(x)
+        Si(x) = pi/2 - f(x) cos(x) - g(x) sin(x)
+
+
+        ACCURACY:
+           Test interval = [0,50].
+        Absolute error, except relative when > 1:
+        arithmetic   function   # trials      peak         rms
+           IEEE        Si        30000       4.4e-16     7.3e-17
+           IEEE        Ci        30000       6.9e-16     5.1e-17
+
+        Cephes Math Library Release 2.1:  January, 1989
+        Copyright 1984, 1987, 1989 by Stephen L. Moshier
+        *************************************************************************/
+        public static void sinecosineintegrals(double x,
+            ref double si,
+            ref double ci)
+        {
+            double z = 0;
+            double c = 0;
+            double s = 0;
+            double f = 0;
+            double g = 0;
+            int sg = 0;
+            double sn = 0;
+            double sd = 0;
+            double cn = 0;
+            double cd = 0;
+            double fn = 0;
+            double fd = 0;
+            double gn = 0;
+            double gd = 0;
+
+            si = 0;
+            ci = 0;
+
+            if( (double)(x)<(double)(0) )
+            {
+                sg = -1;
+                x = -x;
+            }
+            else
+            {
+                sg = 0;
+            }
+            if( (double)(x)==(double)(0) )
+            {
+                si = 0;
+                ci = -math.maxrealnumber;
+                return;
+            }
+            if( (double)(x)>(double)(1.0E9) )
+            {
+                si = 1.570796326794896619-Math.Cos(x)/x;
+                ci = Math.Sin(x)/x;
+                return;
+            }
+            if( (double)(x)<=(double)(4) )
+            {
+                z = x*x;
+                sn = -8.39167827910303881427E-11;
+                sn = sn*z+4.62591714427012837309E-8;
+                sn = sn*z-9.75759303843632795789E-6;
+                sn = sn*z+9.76945438170435310816E-4;
+                sn = sn*z-4.13470316229406538752E-2;
+                sn = sn*z+1.00000000000000000302E0;
+                sd = 2.03269266195951942049E-12;
+                sd = sd*z+1.27997891179943299903E-9;
+                sd = sd*z+4.41827842801218905784E-7;
+                sd = sd*z+9.96412122043875552487E-5;
+                sd = sd*z+1.42085239326149893930E-2;
+                sd = sd*z+9.99999999999999996984E-1;
+                s = x*sn/sd;
+                cn = 2.02524002389102268789E-11;
+                cn = cn*z-1.35249504915790756375E-8;
+                cn = cn*z+3.59325051419993077021E-6;
+                cn = cn*z-4.74007206873407909465E-4;
+                cn = cn*z+2.89159652607555242092E-2;
+                cn = cn*z-1.00000000000000000080E0;
+                cd = 4.07746040061880559506E-12;
+                cd = cd*z+3.06780997581887812692E-9;
+                cd = cd*z+1.23210355685883423679E-6;
+                cd = cd*z+3.17442024775032769882E-4;
+                cd = cd*z+5.10028056236446052392E-2;
+                cd = cd*z+4.00000000000000000080E0;
+                c = z*cn/cd;
+                if( sg!=0 )
+                {
+                    s = -s;
+                }
+                si = s;
+                ci = 0.57721566490153286061+Math.Log(x)+c;
+                return;
+            }
+            s = Math.Sin(x);
+            c = Math.Cos(x);
+            z = 1.0/(x*x);
+            if( (double)(x)<(double)(8) )
+            {
+                fn = 4.23612862892216586994E0;
+                fn = fn*z+5.45937717161812843388E0;
+                fn = fn*z+1.62083287701538329132E0;
+                fn = fn*z+1.67006611831323023771E-1;
+                fn = fn*z+6.81020132472518137426E-3;
+                fn = fn*z+1.08936580650328664411E-4;
+                fn = fn*z+5.48900223421373614008E-7;
+                fd = 1.00000000000000000000E0;
+                fd = fd*z+8.16496634205391016773E0;
+                fd = fd*z+7.30828822505564552187E0;
+                fd = fd*z+1.86792257950184183883E0;
+                fd = fd*z+1.78792052963149907262E-1;
+                fd = fd*z+7.01710668322789753610E-3;
+                fd = fd*z+1.10034357153915731354E-4;
+                fd = fd*z+5.48900252756255700982E-7;
+                f = fn/(x*fd);
+                gn = 8.71001698973114191777E-2;
+                gn = gn*z+6.11379109952219284151E-1;
+                gn = gn*z+3.97180296392337498885E-1;
+                gn = gn*z+7.48527737628469092119E-2;
+                gn = gn*z+5.38868681462177273157E-3;
+                gn = gn*z+1.61999794598934024525E-4;
+                gn = gn*z+1.97963874140963632189E-6;
+                gn = gn*z+7.82579040744090311069E-9;
+                gd = 1.00000000000000000000E0;
+                gd = gd*z+1.64402202413355338886E0;
+                gd = gd*z+6.66296701268987968381E-1;
+                gd = gd*z+9.88771761277688796203E-2;
+                gd = gd*z+6.22396345441768420760E-3;
+                gd = gd*z+1.73221081474177119497E-4;
+                gd = gd*z+2.02659182086343991969E-6;
+                gd = gd*z+7.82579218933534490868E-9;
+                g = z*gn/gd;
+            }
+            else
+            {
+                fn = 4.55880873470465315206E-1;
+                fn = fn*z+7.13715274100146711374E-1;
+                fn = fn*z+1.60300158222319456320E-1;
+                fn = fn*z+1.16064229408124407915E-2;
+                fn = fn*z+3.49556442447859055605E-4;
+                fn = fn*z+4.86215430826454749482E-6;
+                fn = fn*z+3.20092790091004902806E-8;
+                fn = fn*z+9.41779576128512936592E-11;
+                fn = fn*z+9.70507110881952024631E-14;
+                fd = 1.00000000000000000000E0;
+                fd = fd*z+9.17463611873684053703E-1;
+                fd = fd*z+1.78685545332074536321E-1;
+                fd = fd*z+1.22253594771971293032E-2;
+                fd = fd*z+3.58696481881851580297E-4;
+                fd = fd*z+4.92435064317881464393E-6;
+                fd = fd*z+3.21956939101046018377E-8;
+                fd = fd*z+9.43720590350276732376E-11;
+                fd = fd*z+9.70507110881952025725E-14;
+                f = fn/(x*fd);
+                gn = 6.97359953443276214934E-1;
+                gn = gn*z+3.30410979305632063225E-1;
+                gn = gn*z+3.84878767649974295920E-2;
+                gn = gn*z+1.71718239052347903558E-3;
+                gn = gn*z+3.48941165502279436777E-5;
+                gn = gn*z+3.47131167084116673800E-7;
+                gn = gn*z+1.70404452782044526189E-9;
+                gn = gn*z+3.85945925430276600453E-12;
+                gn = gn*z+3.14040098946363334640E-15;
+                gd = 1.00000000000000000000E0;
+                gd = gd*z+1.68548898811011640017E0;
+                gd = gd*z+4.87852258695304967486E-1;
+                gd = gd*z+4.67913194259625806320E-2;
+                gd = gd*z+1.90284426674399523638E-3;
+                gd = gd*z+3.68475504442561108162E-5;
+                gd = gd*z+3.57043223443740838771E-7;
+                gd = gd*z+1.72693748966316146736E-9;
+                gd = gd*z+3.87830166023954706752E-12;
+                gd = gd*z+3.14040098946363335242E-15;
+                g = z*gn/gd;
+            }
+            si = 1.570796326794896619-f*c-g*s;
+            if( sg!=0 )
+            {
+                si = -si;
+            }
+            ci = f*s-g*c;
+        }
+
+
+        /*************************************************************************
+        Hyperbolic sine and cosine integrals
+
+        Approximates the integrals
+
+                                   x
+                                   -
+                                  | |   cosh t - 1
+          Chi(x) = eul + ln x +   |    -----------  dt,
+                                | |          t
+                                 -
+                                 0
+
+                      x
+                      -
+                     | |  sinh t
+          Shi(x) =   |    ------  dt
+                   | |       t
+                    -
+                    0
+
+        where eul = 0.57721566490153286061 is Euler's constant.
+        The integrals are evaluated by power series for x < 8
+        and by Chebyshev expansions for x between 8 and 88.
+        For large x, both functions approach exp(x)/2x.
+        Arguments greater than 88 in magnitude return MAXNUM.
+
+
+        ACCURACY:
+
+        Test interval 0 to 88.
+                             Relative error:
+        arithmetic   function  # trials      peak         rms
+           IEEE         Shi      30000       6.9e-16     1.6e-16
+               Absolute error, except relative when |Chi| > 1:
+           IEEE         Chi      30000       8.4e-16     1.4e-16
+
+        Cephes Math Library Release 2.8:  June, 2000
+        Copyright 1984, 1987, 2000 by Stephen L. Moshier
+        *************************************************************************/
+        public static void hyperbolicsinecosineintegrals(double x,
+            ref double shi,
+            ref double chi)
+        {
+            double k = 0;
+            double z = 0;
+            double c = 0;
+            double s = 0;
+            double a = 0;
+            int sg = 0;
+            double b0 = 0;
+            double b1 = 0;
+            double b2 = 0;
+
+            shi = 0;
+            chi = 0;
+
+            if( (double)(x)<(double)(0) )
+            {
+                sg = -1;
+                x = -x;
+            }
+            else
+            {
+                sg = 0;
+            }
+            if( (double)(x)==(double)(0) )
+            {
+                shi = 0;
+                chi = -math.maxrealnumber;
+                return;
+            }
+            if( (double)(x)<(double)(8.0) )
+            {
+                z = x*x;
+                a = 1.0;
+                s = 1.0;
+                c = 0.0;
+                k = 2.0;
+                do
+                {
+                    a = a*z/k;
+                    c = c+a/k;
+                    k = k+1.0;
+                    a = a/k;
+                    s = s+a/k;
+                    k = k+1.0;
+                }
+                while( (double)(Math.Abs(a/s))>=(double)(math.machineepsilon) );
+                s = s*x;
+            }
+            else
+            {
+                if( (double)(x)<(double)(18.0) )
+                {
+                    a = (576.0/x-52.0)/10.0;
+                    k = Math.Exp(x)/x;
+                    b0 = 1.83889230173399459482E-17;
+                    b1 = 0.0;
+                    chebiterationshichi(a, -9.55485532279655569575E-17, ref b0, ref b1, ref b2);
+                    chebiterationshichi(a, 2.04326105980879882648E-16, ref b0, ref b1, ref b2);
+                    chebiterationshichi(a, 1.09896949074905343022E-15, ref b0, ref b1, ref b2);
+                    chebiterationshichi(a, -1.31313534344092599234E-14, ref b0, ref b1, ref b2);
+                    chebiterationshichi(a, 5.93976226264314278932E-14, ref b0, ref b1, ref b2);
+                    chebiterationshichi(a, -3.47197010497749154755E-14, ref b0, ref b1, ref b2);
+                    chebiterationshichi(a, -1.40059764613117131000E-12, ref b0, ref b1, ref b2);
+                    chebiterationshichi(a, 9.49044626224223543299E-12, ref b0, ref b1, ref b2);
+                    chebiterationshichi(a, -1.61596181145435454033E-11, ref b0, ref b1, ref b2);
+                    chebiterationshichi(a, -1.77899784436430310321E-10, ref b0, ref b1, ref b2);
+                    chebiterationshichi(a, 1.35455469767246947469E-9, ref b0, ref b1, ref b2);
+                    chebiterationshichi(a, -1.03257121792819495123E-9, ref b0, ref b1, ref b2);
+                    chebiterationshichi(a, -3.56699611114982536845E-8, ref b0, ref b1, ref b2);
+                    chebiterationshichi(a, 1.44818877384267342057E-7, ref b0, ref b1, ref b2);
+                    chebiterationshichi(a, 7.82018215184051295296E-7, ref b0, ref b1, ref b2);
+                    chebiterationshichi(a, -5.39919118403805073710E-6, ref b0, ref b1, ref b2);
+                    chebiterationshichi(a, -3.12458202168959833422E-5, ref b0, ref b1, ref b2);
+                    chebiterationshichi(a, 8.90136741950727517826E-5, ref b0, ref b1, ref b2);
+                    chebiterationshichi(a, 2.02558474743846862168E-3, ref b0, ref b1, ref b2);
+                    chebiterationshichi(a, 2.96064440855633256972E-2, ref b0, ref b1, ref b2);
+                    chebiterationshichi(a, 1.11847751047257036625E0, ref b0, ref b1, ref b2);
+                    s = k*0.5*(b0-b2);
+                    b0 = -8.12435385225864036372E-18;
+                    b1 = 0.0;
+                    chebiterationshichi(a, 2.17586413290339214377E-17, ref b0, ref b1, ref b2);
+                    chebiterationshichi(a, 5.22624394924072204667E-17, ref b0, ref b1, ref b2);
+                    chebiterationshichi(a, -9.48812110591690559363E-16, ref b0, ref b1, ref b2);
+                    chebiterationshichi(a, 5.35546311647465209166E-15, ref b0, ref b1, ref b2);
+                    chebiterationshichi(a, -1.21009970113732918701E-14, ref b0, ref b1, ref b2);
+                    chebiterationshichi(a, -6.00865178553447437951E-14, ref b0, ref b1, ref b2);
+                    chebiterationshichi(a, 7.16339649156028587775E-13, ref b0, ref b1, ref b2);
+                    chebiterationshichi(a, -2.93496072607599856104E-12, ref b0, ref b1, ref b2);
+                    chebiterationshichi(a, -1.40359438136491256904E-12, ref b0, ref b1, ref b2);
+                    chebiterationshichi(a, 8.76302288609054966081E-11, ref b0, ref b1, ref b2);
+                    chebiterationshichi(a, -4.40092476213282340617E-10, ref b0, ref b1, ref b2);
+                    chebiterationshichi(a, -1.87992075640569295479E-10, ref b0, ref b1, ref b2);
+                    chebiterationshichi(a, 1.31458150989474594064E-8, ref b0, ref b1, ref b2);
+                    chebiterationshichi(a, -4.75513930924765465590E-8, ref b0, ref b1, ref b2);
+                    chebiterationshichi(a, -2.21775018801848880741E-7, ref b0, ref b1, ref b2);
+                    chebiterationshichi(a, 1.94635531373272490962E-6, ref b0, ref b1, ref b2);
+                    chebiterationshichi(a, 4.33505889257316408893E-6, ref b0, ref b1, ref b2);
+                    chebiterationshichi(a, -6.13387001076494349496E-5, ref b0, ref b1, ref b2);
+                    chebiterationshichi(a, -3.13085477492997465138E-4, ref b0, ref b1, ref b2);
+                    chebiterationshichi(a, 4.97164789823116062801E-4, ref b0, ref b1, ref b2);
+                    chebiterationshichi(a, 2.64347496031374526641E-2, ref b0, ref b1, ref b2);
+                    chebiterationshichi(a, 1.11446150876699213025E0, ref b0, ref b1, ref b2);
+                    c = k*0.5*(b0-b2);
+                }
+                else
+                {
+                    if( (double)(x)<=(double)(88.0) )
+                    {
+                        a = (6336.0/x-212.0)/70.0;
+                        k = Math.Exp(x)/x;
+                        b0 = -1.05311574154850938805E-17;
+                        b1 = 0.0;
+                        chebiterationshichi(a, 2.62446095596355225821E-17, ref b0, ref b1, ref b2);
+                        chebiterationshichi(a, 8.82090135625368160657E-17, ref b0, ref b1, ref b2);
+                        chebiterationshichi(a, -3.38459811878103047136E-16, ref b0, ref b1, ref b2);
+                        chebiterationshichi(a, -8.30608026366935789136E-16, ref b0, ref b1, ref b2);
+                        chebiterationshichi(a, 3.93397875437050071776E-15, ref b0, ref b1, ref b2);
+                        chebiterationshichi(a, 1.01765565969729044505E-14, ref b0, ref b1, ref b2);
+                        chebiterationshichi(a, -4.21128170307640802703E-14, ref b0, ref b1, ref b2);
+                        chebiterationshichi(a, -1.60818204519802480035E-13, ref b0, ref b1, ref b2);
+                        chebiterationshichi(a, 3.34714954175994481761E-13, ref b0, ref b1, ref b2);
+                        chebiterationshichi(a, 2.72600352129153073807E-12, ref b0, ref b1, ref b2);
+                        chebiterationshichi(a, 1.66894954752839083608E-12, ref b0, ref b1, ref b2);
+                        chebiterationshichi(a, -3.49278141024730899554E-11, ref b0, ref b1, ref b2);
+                        chebiterationshichi(a, -1.58580661666482709598E-10, ref b0, ref b1, ref b2);
+                        chebiterationshichi(a, -1.79289437183355633342E-10, ref b0, ref b1, ref b2);
+                        chebiterationshichi(a, 1.76281629144264523277E-9, ref b0, ref b1, ref b2);
+                        chebiterationshichi(a, 1.69050228879421288846E-8, ref b0, ref b1, ref b2);
+                        chebiterationshichi(a, 1.25391771228487041649E-7, ref b0, ref b1, ref b2);
+                        chebiterationshichi(a, 1.16229947068677338732E-6, ref b0, ref b1, ref b2);
+                        chebiterationshichi(a, 1.61038260117376323993E-5, ref b0, ref b1, ref b2);
+                        chebiterationshichi(a, 3.49810375601053973070E-4, ref b0, ref b1, ref b2);
+                        chebiterationshichi(a, 1.28478065259647610779E-2, ref b0, ref b1, ref b2);
+                        chebiterationshichi(a, 1.03665722588798326712E0, ref b0, ref b1, ref b2);
+                        s = k*0.5*(b0-b2);
+                        b0 = 8.06913408255155572081E-18;
+                        b1 = 0.0;
+                        chebiterationshichi(a, -2.08074168180148170312E-17, ref b0, ref b1, ref b2);
+                        chebiterationshichi(a, -5.98111329658272336816E-17, ref b0, ref b1, ref b2);
+                        chebiterationshichi(a, 2.68533951085945765591E-16, ref b0, ref b1, ref b2);
+                        chebiterationshichi(a, 4.52313941698904694774E-16, ref b0, ref b1, ref b2);
+                        chebiterationshichi(a, -3.10734917335299464535E-15, ref b0, ref b1, ref b2);
+                        chebiterationshichi(a, -4.42823207332531972288E-15, ref b0, ref b1, ref b2);
+                        chebiterationshichi(a, 3.49639695410806959872E-14, ref b0, ref b1, ref b2);
+                        chebiterationshichi(a, 6.63406731718911586609E-14, ref b0, ref b1, ref b2);
+                        chebiterationshichi(a, -3.71902448093119218395E-13, ref b0, ref b1, ref b2);
+                        chebiterationshichi(a, -1.27135418132338309016E-12, ref b0, ref b1, ref b2);
+                        chebiterationshichi(a, 2.74851141935315395333E-12, ref b0, ref b1, ref b2);
+                        chebiterationshichi(a, 2.33781843985453438400E-11, ref b0, ref b1, ref b2);
+                        chebiterationshichi(a, 2.71436006377612442764E-11, ref b0, ref b1, ref b2);
+                        chebiterationshichi(a, -2.56600180000355990529E-10, ref b0, ref b1, ref b2);
+                        chebiterationshichi(a, -1.61021375163803438552E-9, ref b0, ref b1, ref b2);
+                        chebiterationshichi(a, -4.72543064876271773512E-9, ref b0, ref b1, ref b2);
+                        chebiterationshichi(a, -3.00095178028681682282E-9, ref b0, ref b1, ref b2);
+                        chebiterationshichi(a, 7.79387474390914922337E-8, ref b0, ref b1, ref b2);
+                        chebiterationshichi(a, 1.06942765566401507066E-6, ref b0, ref b1, ref b2);
+                        chebiterationshichi(a, 1.59503164802313196374E-5, ref b0, ref b1, ref b2);
+                        chebiterationshichi(a, 3.49592575153777996871E-4, ref b0, ref b1, ref b2);
+                        chebiterationshichi(a, 1.28475387530065247392E-2, ref b0, ref b1, ref b2);
+                        chebiterationshichi(a, 1.03665693917934275131E0, ref b0, ref b1, ref b2);
+                        c = k*0.5*(b0-b2);
+                    }
+                    else
+                    {
+                        if( sg!=0 )
+                        {
+                            shi = -math.maxrealnumber;
+                        }
+                        else
+                        {
+                            shi = math.maxrealnumber;
+                        }
+                        chi = math.maxrealnumber;
+                        return;
+                    }
+                }
+            }
+            if( sg!=0 )
+            {
+                s = -s;
+            }
+            shi = s;
+            chi = 0.57721566490153286061+Math.Log(x)+c;
+        }
+
+
+        private static void chebiterationshichi(double x,
+            double c,
+            ref double b0,
+            ref double b1,
+            ref double b2)
+        {
+            b2 = b1;
+            b1 = b0;
+            b0 = x*b1-b2+c;
+        }
+
+
+    }
+}
+
Index: /trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.5.0/ALGLIB-3.5.0/statistics.cs
===================================================================
--- /trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.5.0/ALGLIB-3.5.0/statistics.cs	(revision 7673)
+++ /trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.5.0/ALGLIB-3.5.0/statistics.cs	(revision 7673)
@@ -0,0 +1,17239 @@
+/*************************************************************************
+Copyright (c) Sergey Bochkanov (ALGLIB project).
+
+>>> SOURCE LICENSE >>>
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation (www.fsf.org); either version 2 of the
+License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+A copy of the GNU General Public License is available at
+http://www.fsf.org/licensing/licenses
+>>> END OF LICENSE >>>
+*************************************************************************/
+#pragma warning disable 162
+#pragma warning disable 219
+using System;
+
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    Calculation of the distribution moments: mean, variance, skewness, kurtosis.
+
+    INPUT PARAMETERS:
+        X       -   sample
+        N       -   N>=0, sample size:
+                    * if given, only leading N elements of X are processed
+                    * if not given, automatically determined from size of X
+
+    OUTPUT PARAMETERS
+        Mean    -   mean.
+        Variance-   variance.
+        Skewness-   skewness (if variance<>0; zero otherwise).
+        Kurtosis-   kurtosis (if variance<>0; zero otherwise).
+
+
+      -- ALGLIB --
+         Copyright 06.09.2006 by Bochkanov Sergey
+    *************************************************************************/
+    public static void samplemoments(double[] x, int n, out double mean, out double variance, out double skewness, out double kurtosis)
+    {
+        mean = 0;
+        variance = 0;
+        skewness = 0;
+        kurtosis = 0;
+        basestat.samplemoments(x, n, ref mean, ref variance, ref skewness, ref kurtosis);
+        return;
+    }
+    public static void samplemoments(double[] x, out double mean, out double variance, out double skewness, out double kurtosis)
+    {
+        int n;
+
+        mean = 0;
+        variance = 0;
+        skewness = 0;
+        kurtosis = 0;
+        n = ap.len(x);
+        basestat.samplemoments(x, n, ref mean, ref variance, ref skewness, ref kurtosis);
+
+        return;
+    }
+
+    /*************************************************************************
+    Calculation of the mean.
+
+    INPUT PARAMETERS:
+        X       -   sample
+        N       -   N>=0, sample size:
+                    * if given, only leading N elements of X are processed
+                    * if not given, automatically determined from size of X
+
+    NOTE:
+
+    This function return result  which calculated by 'SampleMoments' function
+    and stored at 'Mean' variable.
+
+
+      -- ALGLIB --
+         Copyright 06.09.2006 by Bochkanov Sergey
+    *************************************************************************/
+    public static double samplemean(double[] x, int n)
+    {
+
+        double result = basestat.samplemean(x, n);
+        return result;
+    }
+    public static double samplemean(double[] x)
+    {
+        int n;
+
+
+        n = ap.len(x);
+        double result = basestat.samplemean(x, n);
+
+        return result;
+    }
+
+    /*************************************************************************
+    Calculation of the variance.
+
+    INPUT PARAMETERS:
+        X       -   sample
+        N       -   N>=0, sample size:
+                    * if given, only leading N elements of X are processed
+                    * if not given, automatically determined from size of X
+
+    NOTE:
+
+    This function return result  which calculated by 'SampleMoments' function
+    and stored at 'Variance' variable.
+
+
+      -- ALGLIB --
+         Copyright 06.09.2006 by Bochkanov Sergey
+    *************************************************************************/
+    public static double samplevariance(double[] x, int n)
+    {
+
+        double result = basestat.samplevariance(x, n);
+        return result;
+    }
+    public static double samplevariance(double[] x)
+    {
+        int n;
+
+
+        n = ap.len(x);
+        double result = basestat.samplevariance(x, n);
+
+        return result;
+    }
+
+    /*************************************************************************
+    Calculation of the skewness.
+
+    INPUT PARAMETERS:
+        X       -   sample
+        N       -   N>=0, sample size:
+                    * if given, only leading N elements of X are processed
+                    * if not given, automatically determined from size of X
+
+    NOTE:
+
+    This function return result  which calculated by 'SampleMoments' function
+    and stored at 'Skewness' variable.
+
+
+      -- ALGLIB --
+         Copyright 06.09.2006 by Bochkanov Sergey
+    *************************************************************************/
+    public static double sampleskewness(double[] x, int n)
+    {
+
+        double result = basestat.sampleskewness(x, n);
+        return result;
+    }
+    public static double sampleskewness(double[] x)
+    {
+        int n;
+
+
+        n = ap.len(x);
+        double result = basestat.sampleskewness(x, n);
+
+        return result;
+    }
+
+    /*************************************************************************
+    Calculation of the kurtosis.
+
+    INPUT PARAMETERS:
+        X       -   sample
+        N       -   N>=0, sample size:
+                    * if given, only leading N elements of X are processed
+                    * if not given, automatically determined from size of X
+
+    NOTE:
+
+    This function return result  which calculated by 'SampleMoments' function
+    and stored at 'Kurtosis' variable.
+
+
+      -- ALGLIB --
+         Copyright 06.09.2006 by Bochkanov Sergey
+    *************************************************************************/
+    public static double samplekurtosis(double[] x, int n)
+    {
+
+        double result = basestat.samplekurtosis(x, n);
+        return result;
+    }
+    public static double samplekurtosis(double[] x)
+    {
+        int n;
+
+
+        n = ap.len(x);
+        double result = basestat.samplekurtosis(x, n);
+
+        return result;
+    }
+
+    /*************************************************************************
+    ADev
+
+    Input parameters:
+        X   -   sample
+        N   -   N>=0, sample size:
+                * if given, only leading N elements of X are processed
+                * if not given, automatically determined from size of X
+
+    Output parameters:
+        ADev-   ADev
+
+      -- ALGLIB --
+         Copyright 06.09.2006 by Bochkanov Sergey
+    *************************************************************************/
+    public static void sampleadev(double[] x, int n, out double adev)
+    {
+        adev = 0;
+        basestat.sampleadev(x, n, ref adev);
+        return;
+    }
+    public static void sampleadev(double[] x, out double adev)
+    {
+        int n;
+
+        adev = 0;
+        n = ap.len(x);
+        basestat.sampleadev(x, n, ref adev);
+
+        return;
+    }
+
+    /*************************************************************************
+    Median calculation.
+
+    Input parameters:
+        X   -   sample (array indexes: [0..N-1])
+        N   -   N>=0, sample size:
+                * if given, only leading N elements of X are processed
+                * if not given, automatically determined from size of X
+
+    Output parameters:
+        Median
+
+      -- ALGLIB --
+         Copyright 06.09.2006 by Bochkanov Sergey
+    *************************************************************************/
+    public static void samplemedian(double[] x, int n, out double median)
+    {
+        median = 0;
+        basestat.samplemedian(x, n, ref median);
+        return;
+    }
+    public static void samplemedian(double[] x, out double median)
+    {
+        int n;
+
+        median = 0;
+        n = ap.len(x);
+        basestat.samplemedian(x, n, ref median);
+
+        return;
+    }
+
+    /*************************************************************************
+    Percentile calculation.
+
+    Input parameters:
+        X   -   sample (array indexes: [0..N-1])
+        N   -   N>=0, sample size:
+                * if given, only leading N elements of X are processed
+                * if not given, automatically determined from size of X
+        P   -   percentile (0<=P<=1)
+
+    Output parameters:
+        V   -   percentile
+
+      -- ALGLIB --
+         Copyright 01.03.2008 by Bochkanov Sergey
+    *************************************************************************/
+    public static void samplepercentile(double[] x, int n, double p, out double v)
+    {
+        v = 0;
+        basestat.samplepercentile(x, n, p, ref v);
+        return;
+    }
+    public static void samplepercentile(double[] x, double p, out double v)
+    {
+        int n;
+
+        v = 0;
+        n = ap.len(x);
+        basestat.samplepercentile(x, n, p, ref v);
+
+        return;
+    }
+
+    /*************************************************************************
+    2-sample covariance
+
+    Input parameters:
+        X       -   sample 1 (array indexes: [0..N-1])
+        Y       -   sample 2 (array indexes: [0..N-1])
+        N       -   N>=0, sample size:
+                    * if given, only N leading elements of X/Y are processed
+                    * if not given, automatically determined from input sizes
+
+    Result:
+        covariance (zero for N=0 or N=1)
+
+      -- ALGLIB --
+         Copyright 28.10.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static double cov2(double[] x, double[] y, int n)
+    {
+
+        double result = basestat.cov2(x, y, n);
+        return result;
+    }
+    public static double cov2(double[] x, double[] y)
+    {
+        int n;
+        if( (ap.len(x)!=ap.len(y)))
+            throw new alglibexception("Error while calling 'cov2': looks like one of arguments has wrong size");
+
+        n = ap.len(x);
+        double result = basestat.cov2(x, y, n);
+
+        return result;
+    }
+
+    /*************************************************************************
+    Pearson product-moment correlation coefficient
+
+    Input parameters:
+        X       -   sample 1 (array indexes: [0..N-1])
+        Y       -   sample 2 (array indexes: [0..N-1])
+        N       -   N>=0, sample size:
+                    * if given, only N leading elements of X/Y are processed
+                    * if not given, automatically determined from input sizes
+
+    Result:
+        Pearson product-moment correlation coefficient
+        (zero for N=0 or N=1)
+
+      -- ALGLIB --
+         Copyright 28.10.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static double pearsoncorr2(double[] x, double[] y, int n)
+    {
+
+        double result = basestat.pearsoncorr2(x, y, n);
+        return result;
+    }
+    public static double pearsoncorr2(double[] x, double[] y)
+    {
+        int n;
+        if( (ap.len(x)!=ap.len(y)))
+            throw new alglibexception("Error while calling 'pearsoncorr2': looks like one of arguments has wrong size");
+
+        n = ap.len(x);
+        double result = basestat.pearsoncorr2(x, y, n);
+
+        return result;
+    }
+
+    /*************************************************************************
+    Spearman's rank correlation coefficient
+
+    Input parameters:
+        X       -   sample 1 (array indexes: [0..N-1])
+        Y       -   sample 2 (array indexes: [0..N-1])
+        N       -   N>=0, sample size:
+                    * if given, only N leading elements of X/Y are processed
+                    * if not given, automatically determined from input sizes
+
+    Result:
+        Spearman's rank correlation coefficient
+        (zero for N=0 or N=1)
+
+      -- ALGLIB --
+         Copyright 09.04.2007 by Bochkanov Sergey
+    *************************************************************************/
+    public static double spearmancorr2(double[] x, double[] y, int n)
+    {
+
+        double result = basestat.spearmancorr2(x, y, n);
+        return result;
+    }
+    public static double spearmancorr2(double[] x, double[] y)
+    {
+        int n;
+        if( (ap.len(x)!=ap.len(y)))
+            throw new alglibexception("Error while calling 'spearmancorr2': looks like one of arguments has wrong size");
+
+        n = ap.len(x);
+        double result = basestat.spearmancorr2(x, y, n);
+
+        return result;
+    }
+
+    /*************************************************************************
+    Covariance matrix
+
+    INPUT PARAMETERS:
+        X   -   array[N,M], sample matrix:
+                * J-th column corresponds to J-th variable
+                * I-th row corresponds to I-th observation
+        N   -   N>=0, number of observations:
+                * if given, only leading N rows of X are used
+                * if not given, automatically determined from input size
+        M   -   M>0, number of variables:
+                * if given, only leading M columns of X are used
+                * if not given, automatically determined from input size
+
+    OUTPUT PARAMETERS:
+        C   -   array[M,M], covariance matrix (zero if N=0 or N=1)
+
+      -- ALGLIB --
+         Copyright 28.10.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void covm(double[,] x, int n, int m, out double[,] c)
+    {
+        c = new double[0,0];
+        basestat.covm(x, n, m, ref c);
+        return;
+    }
+    public static void covm(double[,] x, out double[,] c)
+    {
+        int n;
+        int m;
+
+        c = new double[0,0];
+        n = ap.rows(x);
+        m = ap.cols(x);
+        basestat.covm(x, n, m, ref c);
+
+        return;
+    }
+
+    /*************************************************************************
+    Pearson product-moment correlation matrix
+
+    INPUT PARAMETERS:
+        X   -   array[N,M], sample matrix:
+                * J-th column corresponds to J-th variable
+                * I-th row corresponds to I-th observation
+        N   -   N>=0, number of observations:
+                * if given, only leading N rows of X are used
+                * if not given, automatically determined from input size
+        M   -   M>0, number of variables:
+                * if given, only leading M columns of X are used
+                * if not given, automatically determined from input size
+
+    OUTPUT PARAMETERS:
+        C   -   array[M,M], correlation matrix (zero if N=0 or N=1)
+
+      -- ALGLIB --
+         Copyright 28.10.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void pearsoncorrm(double[,] x, int n, int m, out double[,] c)
+    {
+        c = new double[0,0];
+        basestat.pearsoncorrm(x, n, m, ref c);
+        return;
+    }
+    public static void pearsoncorrm(double[,] x, out double[,] c)
+    {
+        int n;
+        int m;
+
+        c = new double[0,0];
+        n = ap.rows(x);
+        m = ap.cols(x);
+        basestat.pearsoncorrm(x, n, m, ref c);
+
+        return;
+    }
+
+    /*************************************************************************
+    Spearman's rank correlation matrix
+
+    INPUT PARAMETERS:
+        X   -   array[N,M], sample matrix:
+                * J-th column corresponds to J-th variable
+                * I-th row corresponds to I-th observation
+        N   -   N>=0, number of observations:
+                * if given, only leading N rows of X are used
+                * if not given, automatically determined from input size
+        M   -   M>0, number of variables:
+                * if given, only leading M columns of X are used
+                * if not given, automatically determined from input size
+
+    OUTPUT PARAMETERS:
+        C   -   array[M,M], correlation matrix (zero if N=0 or N=1)
+
+      -- ALGLIB --
+         Copyright 28.10.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void spearmancorrm(double[,] x, int n, int m, out double[,] c)
+    {
+        c = new double[0,0];
+        basestat.spearmancorrm(x, n, m, ref c);
+        return;
+    }
+    public static void spearmancorrm(double[,] x, out double[,] c)
+    {
+        int n;
+        int m;
+
+        c = new double[0,0];
+        n = ap.rows(x);
+        m = ap.cols(x);
+        basestat.spearmancorrm(x, n, m, ref c);
+
+        return;
+    }
+
+    /*************************************************************************
+    Cross-covariance matrix
+
+    INPUT PARAMETERS:
+        X   -   array[N,M1], sample matrix:
+                * J-th column corresponds to J-th variable
+                * I-th row corresponds to I-th observation
+        Y   -   array[N,M2], sample matrix:
+                * J-th column corresponds to J-th variable
+                * I-th row corresponds to I-th observation
+        N   -   N>=0, number of observations:
+                * if given, only leading N rows of X/Y are used
+                * if not given, automatically determined from input sizes
+        M1  -   M1>0, number of variables in X:
+                * if given, only leading M1 columns of X are used
+                * if not given, automatically determined from input size
+        M2  -   M2>0, number of variables in Y:
+                * if given, only leading M1 columns of X are used
+                * if not given, automatically determined from input size
+
+    OUTPUT PARAMETERS:
+        C   -   array[M1,M2], cross-covariance matrix (zero if N=0 or N=1)
+
+      -- ALGLIB --
+         Copyright 28.10.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void covm2(double[,] x, double[,] y, int n, int m1, int m2, out double[,] c)
+    {
+        c = new double[0,0];
+        basestat.covm2(x, y, n, m1, m2, ref c);
+        return;
+    }
+    public static void covm2(double[,] x, double[,] y, out double[,] c)
+    {
+        int n;
+        int m1;
+        int m2;
+        if( (ap.rows(x)!=ap.rows(y)))
+            throw new alglibexception("Error while calling 'covm2': looks like one of arguments has wrong size");
+        c = new double[0,0];
+        n = ap.rows(x);
+        m1 = ap.cols(x);
+        m2 = ap.cols(y);
+        basestat.covm2(x, y, n, m1, m2, ref c);
+
+        return;
+    }
+
+    /*************************************************************************
+    Pearson product-moment cross-correlation matrix
+
+    INPUT PARAMETERS:
+        X   -   array[N,M1], sample matrix:
+                * J-th column corresponds to J-th variable
+                * I-th row corresponds to I-th observation
+        Y   -   array[N,M2], sample matrix:
+                * J-th column corresponds to J-th variable
+                * I-th row corresponds to I-th observation
+        N   -   N>=0, number of observations:
+                * if given, only leading N rows of X/Y are used
+                * if not given, automatically determined from input sizes
+        M1  -   M1>0, number of variables in X:
+                * if given, only leading M1 columns of X are used
+                * if not given, automatically determined from input size
+        M2  -   M2>0, number of variables in Y:
+                * if given, only leading M1 columns of X are used
+                * if not given, automatically determined from input size
+
+    OUTPUT PARAMETERS:
+        C   -   array[M1,M2], cross-correlation matrix (zero if N=0 or N=1)
+
+      -- ALGLIB --
+         Copyright 28.10.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void pearsoncorrm2(double[,] x, double[,] y, int n, int m1, int m2, out double[,] c)
+    {
+        c = new double[0,0];
+        basestat.pearsoncorrm2(x, y, n, m1, m2, ref c);
+        return;
+    }
+    public static void pearsoncorrm2(double[,] x, double[,] y, out double[,] c)
+    {
+        int n;
+        int m1;
+        int m2;
+        if( (ap.rows(x)!=ap.rows(y)))
+            throw new alglibexception("Error while calling 'pearsoncorrm2': looks like one of arguments has wrong size");
+        c = new double[0,0];
+        n = ap.rows(x);
+        m1 = ap.cols(x);
+        m2 = ap.cols(y);
+        basestat.pearsoncorrm2(x, y, n, m1, m2, ref c);
+
+        return;
+    }
+
+    /*************************************************************************
+    Spearman's rank cross-correlation matrix
+
+    INPUT PARAMETERS:
+        X   -   array[N,M1], sample matrix:
+                * J-th column corresponds to J-th variable
+                * I-th row corresponds to I-th observation
+        Y   -   array[N,M2], sample matrix:
+                * J-th column corresponds to J-th variable
+                * I-th row corresponds to I-th observation
+        N   -   N>=0, number of observations:
+                * if given, only leading N rows of X/Y are used
+                * if not given, automatically determined from input sizes
+        M1  -   M1>0, number of variables in X:
+                * if given, only leading M1 columns of X are used
+                * if not given, automatically determined from input size
+        M2  -   M2>0, number of variables in Y:
+                * if given, only leading M1 columns of X are used
+                * if not given, automatically determined from input size
+
+    OUTPUT PARAMETERS:
+        C   -   array[M1,M2], cross-correlation matrix (zero if N=0 or N=1)
+
+      -- ALGLIB --
+         Copyright 28.10.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void spearmancorrm2(double[,] x, double[,] y, int n, int m1, int m2, out double[,] c)
+    {
+        c = new double[0,0];
+        basestat.spearmancorrm2(x, y, n, m1, m2, ref c);
+        return;
+    }
+    public static void spearmancorrm2(double[,] x, double[,] y, out double[,] c)
+    {
+        int n;
+        int m1;
+        int m2;
+        if( (ap.rows(x)!=ap.rows(y)))
+            throw new alglibexception("Error while calling 'spearmancorrm2': looks like one of arguments has wrong size");
+        c = new double[0,0];
+        n = ap.rows(x);
+        m1 = ap.cols(x);
+        m2 = ap.cols(y);
+        basestat.spearmancorrm2(x, y, n, m1, m2, ref c);
+
+        return;
+    }
+
+    /*************************************************************************
+    Obsolete function, we recommend to use PearsonCorr2().
+
+      -- ALGLIB --
+         Copyright 09.04.2007 by Bochkanov Sergey
+    *************************************************************************/
+    public static double pearsoncorrelation(double[] x, double[] y, int n)
+    {
+
+        double result = basestat.pearsoncorrelation(x, y, n);
+        return result;
+    }
+
+    /*************************************************************************
+    Obsolete function, we recommend to use SpearmanCorr2().
+
+        -- ALGLIB --
+        Copyright 09.04.2007 by Bochkanov Sergey
+    *************************************************************************/
+    public static double spearmanrankcorrelation(double[] x, double[] y, int n)
+    {
+
+        double result = basestat.spearmanrankcorrelation(x, y, n);
+        return result;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    Pearson's correlation coefficient significance test
+
+    This test checks hypotheses about whether X  and  Y  are  samples  of  two
+    continuous  distributions  having  zero  correlation  or   whether   their
+    correlation is non-zero.
+
+    The following tests are performed:
+        * two-tailed test (null hypothesis - X and Y have zero correlation)
+        * left-tailed test (null hypothesis - the correlation  coefficient  is
+          greater than or equal to 0)
+        * right-tailed test (null hypothesis - the correlation coefficient  is
+          less than or equal to 0).
+
+    Requirements:
+        * the number of elements in each sample is not less than 5
+        * normality of distributions of X and Y.
+
+    Input parameters:
+        R   -   Pearson's correlation coefficient for X and Y
+        N   -   number of elements in samples, N>=5.
+
+    Output parameters:
+        BothTails   -   p-value for two-tailed test.
+                        If BothTails is less than the given significance level
+                        the null hypothesis is rejected.
+        LeftTail    -   p-value for left-tailed test.
+                        If LeftTail is less than the given significance level,
+                        the null hypothesis is rejected.
+        RightTail   -   p-value for right-tailed test.
+                        If RightTail is less than the given significance level
+                        the null hypothesis is rejected.
+
+      -- ALGLIB --
+         Copyright 09.04.2007 by Bochkanov Sergey
+    *************************************************************************/
+    public static void pearsoncorrelationsignificance(double r, int n, out double bothtails, out double lefttail, out double righttail)
+    {
+        bothtails = 0;
+        lefttail = 0;
+        righttail = 0;
+        correlationtests.pearsoncorrelationsignificance(r, n, ref bothtails, ref lefttail, ref righttail);
+        return;
+    }
+
+    /*************************************************************************
+    Spearman's rank correlation coefficient significance test
+
+    This test checks hypotheses about whether X  and  Y  are  samples  of  two
+    continuous  distributions  having  zero  correlation  or   whether   their
+    correlation is non-zero.
+
+    The following tests are performed:
+        * two-tailed test (null hypothesis - X and Y have zero correlation)
+        * left-tailed test (null hypothesis - the correlation  coefficient  is
+          greater than or equal to 0)
+        * right-tailed test (null hypothesis - the correlation coefficient  is
+          less than or equal to 0).
+
+    Requirements:
+        * the number of elements in each sample is not less than 5.
+
+    The test is non-parametric and doesn't require distributions X and Y to be
+    normal.
+
+    Input parameters:
+        R   -   Spearman's rank correlation coefficient for X and Y
+        N   -   number of elements in samples, N>=5.
+
+    Output parameters:
+        BothTails   -   p-value for two-tailed test.
+                        If BothTails is less than the given significance level
+                        the null hypothesis is rejected.
+        LeftTail    -   p-value for left-tailed test.
+                        If LeftTail is less than the given significance level,
+                        the null hypothesis is rejected.
+        RightTail   -   p-value for right-tailed test.
+                        If RightTail is less than the given significance level
+                        the null hypothesis is rejected.
+
+      -- ALGLIB --
+         Copyright 09.04.2007 by Bochkanov Sergey
+    *************************************************************************/
+    public static void spearmanrankcorrelationsignificance(double r, int n, out double bothtails, out double lefttail, out double righttail)
+    {
+        bothtails = 0;
+        lefttail = 0;
+        righttail = 0;
+        correlationtests.spearmanrankcorrelationsignificance(r, n, ref bothtails, ref lefttail, ref righttail);
+        return;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    Jarque-Bera test
+
+    This test checks hypotheses about the fact that a  given  sample  X  is  a
+    sample of normal random variable.
+
+    Requirements:
+        * the number of elements in the sample is not less than 5.
+
+    Input parameters:
+        X   -   sample. Array whose index goes from 0 to N-1.
+        N   -   size of the sample. N>=5
+
+    Output parameters:
+        BothTails   -   p-value for two-tailed test.
+                        If BothTails is less than the given significance level
+                        the null hypothesis is rejected.
+        LeftTail    -   p-value for left-tailed test.
+                        If LeftTail is less than the given significance level,
+                        the null hypothesis is rejected.
+        RightTail   -   p-value for right-tailed test.
+                        If RightTail is less than the given significance level
+                        the null hypothesis is rejected.
+
+    Accuracy of the approximation used (5<=N<=1951):
+
+    p-value  	    relative error (5<=N<=1951)
+    [1, 0.1]            < 1%
+    [0.1, 0.01]         < 2%
+    [0.01, 0.001]       < 6%
+    [0.001, 0]          wasn't measured
+
+    For N>1951 accuracy wasn't measured but it shouldn't be sharply  different
+    from table values.
+
+      -- ALGLIB --
+         Copyright 09.04.2007 by Bochkanov Sergey
+    *************************************************************************/
+    public static void jarqueberatest(double[] x, int n, out double p)
+    {
+        p = 0;
+        jarquebera.jarqueberatest(x, n, ref p);
+        return;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    Mann-Whitney U-test
+
+    This test checks hypotheses about whether X  and  Y  are  samples  of  two
+    continuous distributions of the same shape  and  same  median  or  whether
+    their medians are different.
+
+    The following tests are performed:
+        * two-tailed test (null hypothesis - the medians are equal)
+        * left-tailed test (null hypothesis - the median of the  first  sample
+          is greater than or equal to the median of the second sample)
+        * right-tailed test (null hypothesis - the median of the first  sample
+          is less than or equal to the median of the second sample).
+
+    Requirements:
+        * the samples are independent
+        * X and Y are continuous distributions (or discrete distributions well-
+          approximating continuous distributions)
+        * distributions of X and Y have the  same  shape.  The  only  possible
+          difference is their position (i.e. the value of the median)
+        * the number of elements in each sample is not less than 5
+        * the scale of measurement should be ordinal, interval or ratio  (i.e.
+          the test could not be applied to nominal variables).
+
+    The test is non-parametric and doesn't require distributions to be normal.
+
+    Input parameters:
+        X   -   sample 1. Array whose index goes from 0 to N-1.
+        N   -   size of the sample. N>=5
+        Y   -   sample 2. Array whose index goes from 0 to M-1.
+        M   -   size of the sample. M>=5
+
+    Output parameters:
+        BothTails   -   p-value for two-tailed test.
+                        If BothTails is less than the given significance level
+                        the null hypothesis is rejected.
+        LeftTail    -   p-value for left-tailed test.
+                        If LeftTail is less than the given significance level,
+                        the null hypothesis is rejected.
+        RightTail   -   p-value for right-tailed test.
+                        If RightTail is less than the given significance level
+                        the null hypothesis is rejected.
+
+    To calculate p-values, special approximation is used. This method lets  us
+    calculate p-values with satisfactory  accuracy  in  interval  [0.0001, 1].
+    There is no approximation outside the [0.0001, 1] interval. Therefore,  if
+    the significance level outlies this interval, the test returns 0.0001.
+
+    Relative precision of approximation of p-value:
+
+    N          M          Max.err.   Rms.err.
+    5..10      N..10      1.4e-02    6.0e-04
+    5..10      N..100     2.2e-02    5.3e-06
+    10..15     N..15      1.0e-02    3.2e-04
+    10..15     N..100     1.0e-02    2.2e-05
+    15..100    N..100     6.1e-03    2.7e-06
+
+    For N,M>100 accuracy checks weren't put into  practice,  but  taking  into
+    account characteristics of asymptotic approximation used, precision should
+    not be sharply different from the values for interval [5, 100].
+
+      -- ALGLIB --
+         Copyright 09.04.2007 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mannwhitneyutest(double[] x, int n, double[] y, int m, out double bothtails, out double lefttail, out double righttail)
+    {
+        bothtails = 0;
+        lefttail = 0;
+        righttail = 0;
+        mannwhitneyu.mannwhitneyutest(x, n, y, m, ref bothtails, ref lefttail, ref righttail);
+        return;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    Sign test
+
+    This test checks three hypotheses about the median of  the  given  sample.
+    The following tests are performed:
+        * two-tailed test (null hypothesis - the median is equal to the  given
+          value)
+        * left-tailed test (null hypothesis - the median is  greater  than  or
+          equal to the given value)
+        * right-tailed test (null hypothesis - the  median  is  less  than  or
+          equal to the given value)
+
+    Requirements:
+        * the scale of measurement should be ordinal, interval or ratio  (i.e.
+          the test could not be applied to nominal variables).
+
+    The test is non-parametric and doesn't require distribution X to be normal
+
+    Input parameters:
+        X       -   sample. Array whose index goes from 0 to N-1.
+        N       -   size of the sample.
+        Median  -   assumed median value.
+
+    Output parameters:
+        BothTails   -   p-value for two-tailed test.
+                        If BothTails is less than the given significance level
+                        the null hypothesis is rejected.
+        LeftTail    -   p-value for left-tailed test.
+                        If LeftTail is less than the given significance level,
+                        the null hypothesis is rejected.
+        RightTail   -   p-value for right-tailed test.
+                        If RightTail is less than the given significance level
+                        the null hypothesis is rejected.
+
+    While   calculating   p-values   high-precision   binomial    distribution
+    approximation is used, so significance levels have about 15 exact digits.
+
+      -- ALGLIB --
+         Copyright 08.09.2006 by Bochkanov Sergey
+    *************************************************************************/
+    public static void onesamplesigntest(double[] x, int n, double median, out double bothtails, out double lefttail, out double righttail)
+    {
+        bothtails = 0;
+        lefttail = 0;
+        righttail = 0;
+        stest.onesamplesigntest(x, n, median, ref bothtails, ref lefttail, ref righttail);
+        return;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    One-sample t-test
+
+    This test checks three hypotheses about the mean of the given sample.  The
+    following tests are performed:
+        * two-tailed test (null hypothesis - the mean is equal  to  the  given
+          value)
+        * left-tailed test (null hypothesis - the  mean  is  greater  than  or
+          equal to the given value)
+        * right-tailed test (null hypothesis - the mean is less than or  equal
+          to the given value).
+
+    The test is based on the assumption that  a  given  sample  has  a  normal
+    distribution and  an  unknown  dispersion.  If  the  distribution  sharply
+    differs from normal, the test will work incorrectly.
+
+    Input parameters:
+        X       -   sample. Array whose index goes from 0 to N-1.
+        N       -   size of sample.
+        Mean    -   assumed value of the mean.
+
+    Output parameters:
+        BothTails   -   p-value for two-tailed test.
+                        If BothTails is less than the given significance level
+                        the null hypothesis is rejected.
+        LeftTail    -   p-value for left-tailed test.
+                        If LeftTail is less than the given significance level,
+                        the null hypothesis is rejected.
+        RightTail   -   p-value for right-tailed test.
+                        If RightTail is less than the given significance level
+                        the null hypothesis is rejected.
+
+      -- ALGLIB --
+         Copyright 08.09.2006 by Bochkanov Sergey
+    *************************************************************************/
+    public static void studentttest1(double[] x, int n, double mean, out double bothtails, out double lefttail, out double righttail)
+    {
+        bothtails = 0;
+        lefttail = 0;
+        righttail = 0;
+        studentttests.studentttest1(x, n, mean, ref bothtails, ref lefttail, ref righttail);
+        return;
+    }
+
+    /*************************************************************************
+    Two-sample pooled test
+
+    This test checks three hypotheses about the mean of the given samples. The
+    following tests are performed:
+        * two-tailed test (null hypothesis - the means are equal)
+        * left-tailed test (null hypothesis - the mean of the first sample  is
+          greater than or equal to the mean of the second sample)
+        * right-tailed test (null hypothesis - the mean of the first sample is
+          less than or equal to the mean of the second sample).
+
+    Test is based on the following assumptions:
+        * given samples have normal distributions
+        * dispersions are equal
+        * samples are independent.
+
+    Input parameters:
+        X       -   sample 1. Array whose index goes from 0 to N-1.
+        N       -   size of sample.
+        Y       -   sample 2. Array whose index goes from 0 to M-1.
+        M       -   size of sample.
+
+    Output parameters:
+        BothTails   -   p-value for two-tailed test.
+                        If BothTails is less than the given significance level
+                        the null hypothesis is rejected.
+        LeftTail    -   p-value for left-tailed test.
+                        If LeftTail is less than the given significance level,
+                        the null hypothesis is rejected.
+        RightTail   -   p-value for right-tailed test.
+                        If RightTail is less than the given significance level
+                        the null hypothesis is rejected.
+
+      -- ALGLIB --
+         Copyright 18.09.2006 by Bochkanov Sergey
+    *************************************************************************/
+    public static void studentttest2(double[] x, int n, double[] y, int m, out double bothtails, out double lefttail, out double righttail)
+    {
+        bothtails = 0;
+        lefttail = 0;
+        righttail = 0;
+        studentttests.studentttest2(x, n, y, m, ref bothtails, ref lefttail, ref righttail);
+        return;
+    }
+
+    /*************************************************************************
+    Two-sample unpooled test
+
+    This test checks three hypotheses about the mean of the given samples. The
+    following tests are performed:
+        * two-tailed test (null hypothesis - the means are equal)
+        * left-tailed test (null hypothesis - the mean of the first sample  is
+          greater than or equal to the mean of the second sample)
+        * right-tailed test (null hypothesis - the mean of the first sample is
+          less than or equal to the mean of the second sample).
+
+    Test is based on the following assumptions:
+        * given samples have normal distributions
+        * samples are independent.
+    Dispersion equality is not required
+
+    Input parameters:
+        X - sample 1. Array whose index goes from 0 to N-1.
+        N - size of the sample.
+        Y - sample 2. Array whose index goes from 0 to M-1.
+        M - size of the sample.
+
+    Output parameters:
+        BothTails   -   p-value for two-tailed test.
+                        If BothTails is less than the given significance level
+                        the null hypothesis is rejected.
+        LeftTail    -   p-value for left-tailed test.
+                        If LeftTail is less than the given significance level,
+                        the null hypothesis is rejected.
+        RightTail   -   p-value for right-tailed test.
+                        If RightTail is less than the given significance level
+                        the null hypothesis is rejected.
+
+      -- ALGLIB --
+         Copyright 18.09.2006 by Bochkanov Sergey
+    *************************************************************************/
+    public static void unequalvariancettest(double[] x, int n, double[] y, int m, out double bothtails, out double lefttail, out double righttail)
+    {
+        bothtails = 0;
+        lefttail = 0;
+        righttail = 0;
+        studentttests.unequalvariancettest(x, n, y, m, ref bothtails, ref lefttail, ref righttail);
+        return;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    Two-sample F-test
+
+    This test checks three hypotheses about dispersions of the given  samples.
+    The following tests are performed:
+        * two-tailed test (null hypothesis - the dispersions are equal)
+        * left-tailed test (null hypothesis  -  the  dispersion  of  the first
+          sample is greater than or equal to  the  dispersion  of  the  second
+          sample).
+        * right-tailed test (null hypothesis - the  dispersion  of  the  first
+          sample is less than or equal to the dispersion of the second sample)
+
+    The test is based on the following assumptions:
+        * the given samples have normal distributions
+        * the samples are independent.
+
+    Input parameters:
+        X   -   sample 1. Array whose index goes from 0 to N-1.
+        N   -   sample size.
+        Y   -   sample 2. Array whose index goes from 0 to M-1.
+        M   -   sample size.
+
+    Output parameters:
+        BothTails   -   p-value for two-tailed test.
+                        If BothTails is less than the given significance level
+                        the null hypothesis is rejected.
+        LeftTail    -   p-value for left-tailed test.
+                        If LeftTail is less than the given significance level,
+                        the null hypothesis is rejected.
+        RightTail   -   p-value for right-tailed test.
+                        If RightTail is less than the given significance level
+                        the null hypothesis is rejected.
+
+      -- ALGLIB --
+         Copyright 19.09.2006 by Bochkanov Sergey
+    *************************************************************************/
+    public static void ftest(double[] x, int n, double[] y, int m, out double bothtails, out double lefttail, out double righttail)
+    {
+        bothtails = 0;
+        lefttail = 0;
+        righttail = 0;
+        variancetests.ftest(x, n, y, m, ref bothtails, ref lefttail, ref righttail);
+        return;
+    }
+
+    /*************************************************************************
+    One-sample chi-square test
+
+    This test checks three hypotheses about the dispersion of the given sample
+    The following tests are performed:
+        * two-tailed test (null hypothesis - the dispersion equals  the  given
+          number)
+        * left-tailed test (null hypothesis - the dispersion is  greater  than
+          or equal to the given number)
+        * right-tailed test (null hypothesis  -  dispersion is  less  than  or
+          equal to the given number).
+
+    Test is based on the following assumptions:
+        * the given sample has a normal distribution.
+
+    Input parameters:
+        X           -   sample 1. Array whose index goes from 0 to N-1.
+        N           -   size of the sample.
+        Variance    -   dispersion value to compare with.
+
+    Output parameters:
+        BothTails   -   p-value for two-tailed test.
+                        If BothTails is less than the given significance level
+                        the null hypothesis is rejected.
+        LeftTail    -   p-value for left-tailed test.
+                        If LeftTail is less than the given significance level,
+                        the null hypothesis is rejected.
+        RightTail   -   p-value for right-tailed test.
+                        If RightTail is less than the given significance level
+                        the null hypothesis is rejected.
+
+      -- ALGLIB --
+         Copyright 19.09.2006 by Bochkanov Sergey
+    *************************************************************************/
+    public static void onesamplevariancetest(double[] x, int n, double variance, out double bothtails, out double lefttail, out double righttail)
+    {
+        bothtails = 0;
+        lefttail = 0;
+        righttail = 0;
+        variancetests.onesamplevariancetest(x, n, variance, ref bothtails, ref lefttail, ref righttail);
+        return;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    Wilcoxon signed-rank test
+
+    This test checks three hypotheses about the median  of  the  given sample.
+    The following tests are performed:
+        * two-tailed test (null hypothesis - the median is equal to the  given
+          value)
+        * left-tailed test (null hypothesis - the median is  greater  than  or
+          equal to the given value)
+        * right-tailed test (null hypothesis  -  the  median  is  less than or
+          equal to the given value)
+
+    Requirements:
+        * the scale of measurement should be ordinal, interval or  ratio (i.e.
+          the test could not be applied to nominal variables).
+        * the distribution should be continuous and symmetric relative to  its
+          median.
+        * number of distinct values in the X array should be greater than 4
+
+    The test is non-parametric and doesn't require distribution X to be normal
+
+    Input parameters:
+        X       -   sample. Array whose index goes from 0 to N-1.
+        N       -   size of the sample.
+        Median  -   assumed median value.
+
+    Output parameters:
+        BothTails   -   p-value for two-tailed test.
+                        If BothTails is less than the given significance level
+                        the null hypothesis is rejected.
+        LeftTail    -   p-value for left-tailed test.
+                        If LeftTail is less than the given significance level,
+                        the null hypothesis is rejected.
+        RightTail   -   p-value for right-tailed test.
+                        If RightTail is less than the given significance level
+                        the null hypothesis is rejected.
+
+    To calculate p-values, special approximation is used. This method lets  us
+    calculate p-values with two decimal places in interval [0.0001, 1].
+
+    "Two decimal places" does not sound very impressive, but in  practice  the
+    relative error of less than 1% is enough to make a decision.
+
+    There is no approximation outside the [0.0001, 1] interval. Therefore,  if
+    the significance level outlies this interval, the test returns 0.0001.
+
+      -- ALGLIB --
+         Copyright 08.09.2006 by Bochkanov Sergey
+    *************************************************************************/
+    public static void wilcoxonsignedranktest(double[] x, int n, double e, out double bothtails, out double lefttail, out double righttail)
+    {
+        bothtails = 0;
+        lefttail = 0;
+        righttail = 0;
+        wsr.wilcoxonsignedranktest(x, n, e, ref bothtails, ref lefttail, ref righttail);
+        return;
+    }
+
+}
+public partial class alglib
+{
+    public class basestat
+    {
+        /*************************************************************************
+        Calculation of the distribution moments: mean, variance, skewness, kurtosis.
+
+        INPUT PARAMETERS:
+            X       -   sample
+            N       -   N>=0, sample size:
+                        * if given, only leading N elements of X are processed
+                        * if not given, automatically determined from size of X
+            
+        OUTPUT PARAMETERS
+            Mean    -   mean.
+            Variance-   variance.
+            Skewness-   skewness (if variance<>0; zero otherwise).
+            Kurtosis-   kurtosis (if variance<>0; zero otherwise).
+
+
+          -- ALGLIB --
+             Copyright 06.09.2006 by Bochkanov Sergey
+        *************************************************************************/
+        public static void samplemoments(double[] x,
+            int n,
+            ref double mean,
+            ref double variance,
+            ref double skewness,
+            ref double kurtosis)
+        {
+            int i = 0;
+            double v = 0;
+            double v1 = 0;
+            double v2 = 0;
+            double stddev = 0;
+
+            mean = 0;
+            variance = 0;
+            skewness = 0;
+            kurtosis = 0;
+
+            alglib.ap.assert(n>=0, "SampleMoments: N<0");
+            alglib.ap.assert(alglib.ap.len(x)>=n, "SampleMoments: Length(X)<N!");
+            alglib.ap.assert(apserv.isfinitevector(x, n), "SampleMoments: X is not finite vector");
+            
+            //
+            // Init, special case 'N=0'
+            //
+            mean = 0;
+            variance = 0;
+            skewness = 0;
+            kurtosis = 0;
+            stddev = 0;
+            if( n<=0 )
+            {
+                return;
+            }
+            
+            //
+            // Mean
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                mean = mean+x[i];
+            }
+            mean = mean/n;
+            
+            //
+            // Variance (using corrected two-pass algorithm)
+            //
+            if( n!=1 )
+            {
+                v1 = 0;
+                for(i=0; i<=n-1; i++)
+                {
+                    v1 = v1+math.sqr(x[i]-mean);
+                }
+                v2 = 0;
+                for(i=0; i<=n-1; i++)
+                {
+                    v2 = v2+(x[i]-mean);
+                }
+                v2 = math.sqr(v2)/n;
+                variance = (v1-v2)/(n-1);
+                if( (double)(variance)<(double)(0) )
+                {
+                    variance = 0;
+                }
+                stddev = Math.Sqrt(variance);
+            }
+            
+            //
+            // Skewness and kurtosis
+            //
+            if( (double)(stddev)!=(double)(0) )
+            {
+                for(i=0; i<=n-1; i++)
+                {
+                    v = (x[i]-mean)/stddev;
+                    v2 = math.sqr(v);
+                    skewness = skewness+v2*v;
+                    kurtosis = kurtosis+math.sqr(v2);
+                }
+                skewness = skewness/n;
+                kurtosis = kurtosis/n-3;
+            }
+        }
+
+
+        /*************************************************************************
+        Calculation of the mean.
+
+        INPUT PARAMETERS:
+            X       -   sample
+            N       -   N>=0, sample size:
+                        * if given, only leading N elements of X are processed
+                        * if not given, automatically determined from size of X
+
+        NOTE:
+                        
+        This function return result  which calculated by 'SampleMoments' function
+        and stored at 'Mean' variable.
+
+
+          -- ALGLIB --
+             Copyright 06.09.2006 by Bochkanov Sergey
+        *************************************************************************/
+        public static double samplemean(double[] x,
+            int n)
+        {
+            double result = 0;
+            double mean = 0;
+            double tmp0 = 0;
+            double tmp1 = 0;
+            double tmp2 = 0;
+
+            samplemoments(x, n, ref mean, ref tmp0, ref tmp1, ref tmp2);
+            result = mean;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Calculation of the variance.
+
+        INPUT PARAMETERS:
+            X       -   sample
+            N       -   N>=0, sample size:
+                        * if given, only leading N elements of X are processed
+                        * if not given, automatically determined from size of X
+
+        NOTE:
+                        
+        This function return result  which calculated by 'SampleMoments' function
+        and stored at 'Variance' variable.
+
+
+          -- ALGLIB --
+             Copyright 06.09.2006 by Bochkanov Sergey
+        *************************************************************************/
+        public static double samplevariance(double[] x,
+            int n)
+        {
+            double result = 0;
+            double variance = 0;
+            double tmp0 = 0;
+            double tmp1 = 0;
+            double tmp2 = 0;
+
+            samplemoments(x, n, ref tmp0, ref variance, ref tmp1, ref tmp2);
+            result = variance;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Calculation of the skewness.
+
+        INPUT PARAMETERS:
+            X       -   sample
+            N       -   N>=0, sample size:
+                        * if given, only leading N elements of X are processed
+                        * if not given, automatically determined from size of X
+
+        NOTE:
+                        
+        This function return result  which calculated by 'SampleMoments' function
+        and stored at 'Skewness' variable.
+
+
+          -- ALGLIB --
+             Copyright 06.09.2006 by Bochkanov Sergey
+        *************************************************************************/
+        public static double sampleskewness(double[] x,
+            int n)
+        {
+            double result = 0;
+            double skewness = 0;
+            double tmp0 = 0;
+            double tmp1 = 0;
+            double tmp2 = 0;
+
+            samplemoments(x, n, ref tmp0, ref tmp1, ref skewness, ref tmp2);
+            result = skewness;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Calculation of the kurtosis.
+
+        INPUT PARAMETERS:
+            X       -   sample
+            N       -   N>=0, sample size:
+                        * if given, only leading N elements of X are processed
+                        * if not given, automatically determined from size of X
+
+        NOTE:
+                        
+        This function return result  which calculated by 'SampleMoments' function
+        and stored at 'Kurtosis' variable.
+
+
+          -- ALGLIB --
+             Copyright 06.09.2006 by Bochkanov Sergey
+        *************************************************************************/
+        public static double samplekurtosis(double[] x,
+            int n)
+        {
+            double result = 0;
+            double kurtosis = 0;
+            double tmp0 = 0;
+            double tmp1 = 0;
+            double tmp2 = 0;
+
+            samplemoments(x, n, ref tmp0, ref tmp1, ref tmp2, ref kurtosis);
+            result = kurtosis;
+            return result;
+        }
+
+
+        /*************************************************************************
+        ADev
+
+        Input parameters:
+            X   -   sample
+            N   -   N>=0, sample size:
+                    * if given, only leading N elements of X are processed
+                    * if not given, automatically determined from size of X
+            
+        Output parameters:
+            ADev-   ADev
+
+          -- ALGLIB --
+             Copyright 06.09.2006 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sampleadev(double[] x,
+            int n,
+            ref double adev)
+        {
+            int i = 0;
+            double mean = 0;
+
+            adev = 0;
+
+            alglib.ap.assert(n>=0, "SampleADev: N<0");
+            alglib.ap.assert(alglib.ap.len(x)>=n, "SampleADev: Length(X)<N!");
+            alglib.ap.assert(apserv.isfinitevector(x, n), "SampleADev: X is not finite vector");
+            
+            //
+            // Init, handle N=0
+            //
+            mean = 0;
+            adev = 0;
+            if( n<=0 )
+            {
+                return;
+            }
+            
+            //
+            // Mean
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                mean = mean+x[i];
+            }
+            mean = mean/n;
+            
+            //
+            // ADev
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                adev = adev+Math.Abs(x[i]-mean);
+            }
+            adev = adev/n;
+        }
+
+
+        /*************************************************************************
+        Median calculation.
+
+        Input parameters:
+            X   -   sample (array indexes: [0..N-1])
+            N   -   N>=0, sample size:
+                    * if given, only leading N elements of X are processed
+                    * if not given, automatically determined from size of X
+
+        Output parameters:
+            Median
+
+          -- ALGLIB --
+             Copyright 06.09.2006 by Bochkanov Sergey
+        *************************************************************************/
+        public static void samplemedian(double[] x,
+            int n,
+            ref double median)
+        {
+            int i = 0;
+            int ir = 0;
+            int j = 0;
+            int l = 0;
+            int midp = 0;
+            int k = 0;
+            double a = 0;
+            double tval = 0;
+
+            x = (double[])x.Clone();
+            median = 0;
+
+            alglib.ap.assert(n>=0, "SampleMedian: N<0");
+            alglib.ap.assert(alglib.ap.len(x)>=n, "SampleMedian: Length(X)<N!");
+            alglib.ap.assert(apserv.isfinitevector(x, n), "SampleMedian: X is not finite vector");
+            
+            //
+            // Some degenerate cases
+            //
+            median = 0;
+            if( n<=0 )
+            {
+                return;
+            }
+            if( n==1 )
+            {
+                median = x[0];
+                return;
+            }
+            if( n==2 )
+            {
+                median = 0.5*(x[0]+x[1]);
+                return;
+            }
+            
+            //
+            // Common case, N>=3.
+            // Choose X[(N-1)/2]
+            //
+            l = 0;
+            ir = n-1;
+            k = (n-1)/2;
+            while( true )
+            {
+                if( ir<=l+1 )
+                {
+                    
+                    //
+                    // 1 or 2 elements in partition
+                    //
+                    if( ir==l+1 && (double)(x[ir])<(double)(x[l]) )
+                    {
+                        tval = x[l];
+                        x[l] = x[ir];
+                        x[ir] = tval;
+                    }
+                    break;
+                }
+                else
+                {
+                    midp = (l+ir)/2;
+                    tval = x[midp];
+                    x[midp] = x[l+1];
+                    x[l+1] = tval;
+                    if( (double)(x[l])>(double)(x[ir]) )
+                    {
+                        tval = x[l];
+                        x[l] = x[ir];
+                        x[ir] = tval;
+                    }
+                    if( (double)(x[l+1])>(double)(x[ir]) )
+                    {
+                        tval = x[l+1];
+                        x[l+1] = x[ir];
+                        x[ir] = tval;
+                    }
+                    if( (double)(x[l])>(double)(x[l+1]) )
+                    {
+                        tval = x[l];
+                        x[l] = x[l+1];
+                        x[l+1] = tval;
+                    }
+                    i = l+1;
+                    j = ir;
+                    a = x[l+1];
+                    while( true )
+                    {
+                        do
+                        {
+                            i = i+1;
+                        }
+                        while( (double)(x[i])<(double)(a) );
+                        do
+                        {
+                            j = j-1;
+                        }
+                        while( (double)(x[j])>(double)(a) );
+                        if( j<i )
+                        {
+                            break;
+                        }
+                        tval = x[i];
+                        x[i] = x[j];
+                        x[j] = tval;
+                    }
+                    x[l+1] = x[j];
+                    x[j] = a;
+                    if( j>=k )
+                    {
+                        ir = j-1;
+                    }
+                    if( j<=k )
+                    {
+                        l = i;
+                    }
+                }
+            }
+            
+            //
+            // If N is odd, return result
+            //
+            if( n%2==1 )
+            {
+                median = x[k];
+                return;
+            }
+            a = x[n-1];
+            for(i=k+1; i<=n-1; i++)
+            {
+                if( (double)(x[i])<(double)(a) )
+                {
+                    a = x[i];
+                }
+            }
+            median = 0.5*(x[k]+a);
+        }
+
+
+        /*************************************************************************
+        Percentile calculation.
+
+        Input parameters:
+            X   -   sample (array indexes: [0..N-1])
+            N   -   N>=0, sample size:
+                    * if given, only leading N elements of X are processed
+                    * if not given, automatically determined from size of X
+            P   -   percentile (0<=P<=1)
+
+        Output parameters:
+            V   -   percentile
+
+          -- ALGLIB --
+             Copyright 01.03.2008 by Bochkanov Sergey
+        *************************************************************************/
+        public static void samplepercentile(double[] x,
+            int n,
+            double p,
+            ref double v)
+        {
+            int i1 = 0;
+            double t = 0;
+            double[] rbuf = new double[0];
+
+            x = (double[])x.Clone();
+            v = 0;
+
+            alglib.ap.assert(n>=0, "SamplePercentile: N<0");
+            alglib.ap.assert(alglib.ap.len(x)>=n, "SamplePercentile: Length(X)<N!");
+            alglib.ap.assert(apserv.isfinitevector(x, n), "SamplePercentile: X is not finite vector");
+            alglib.ap.assert(math.isfinite(p), "SamplePercentile: incorrect P!");
+            alglib.ap.assert((double)(p)>=(double)(0) && (double)(p)<=(double)(1), "SamplePercentile: incorrect P!");
+            tsort.tagsortfast(ref x, ref rbuf, n);
+            if( (double)(p)==(double)(0) )
+            {
+                v = x[0];
+                return;
+            }
+            if( (double)(p)==(double)(1) )
+            {
+                v = x[n-1];
+                return;
+            }
+            t = p*(n-1);
+            i1 = (int)Math.Floor(t);
+            t = t-(int)Math.Floor(t);
+            v = x[i1]*(1-t)+x[i1+1]*t;
+        }
+
+
+        /*************************************************************************
+        2-sample covariance
+
+        Input parameters:
+            X       -   sample 1 (array indexes: [0..N-1])
+            Y       -   sample 2 (array indexes: [0..N-1])
+            N       -   N>=0, sample size:
+                        * if given, only N leading elements of X/Y are processed
+                        * if not given, automatically determined from input sizes
+
+        Result:
+            covariance (zero for N=0 or N=1)
+
+          -- ALGLIB --
+             Copyright 28.10.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static double cov2(double[] x,
+            double[] y,
+            int n)
+        {
+            double result = 0;
+            int i = 0;
+            double xmean = 0;
+            double ymean = 0;
+            double v = 0;
+            double x0 = 0;
+            double y0 = 0;
+            double s = 0;
+            bool samex = new bool();
+            bool samey = new bool();
+
+            alglib.ap.assert(n>=0, "Cov2: N<0");
+            alglib.ap.assert(alglib.ap.len(x)>=n, "Cov2: Length(X)<N!");
+            alglib.ap.assert(alglib.ap.len(y)>=n, "Cov2: Length(Y)<N!");
+            alglib.ap.assert(apserv.isfinitevector(x, n), "Cov2: X is not finite vector");
+            alglib.ap.assert(apserv.isfinitevector(y, n), "Cov2: Y is not finite vector");
+            
+            //
+            // Special case
+            //
+            if( n<=1 )
+            {
+                result = 0;
+                return result;
+            }
+            
+            //
+            // Calculate mean.
+            //
+            //
+            // Additonally we calculate SameX and SameY -
+            // flag variables which are set to True when
+            // all X[] (or Y[]) contain exactly same value.
+            //
+            // If at least one of them is True, we return zero
+            // (othwerwise we risk to get nonzero covariation
+            // because of roundoff).
+            //
+            xmean = 0;
+            ymean = 0;
+            samex = true;
+            samey = true;
+            x0 = x[0];
+            y0 = y[0];
+            v = (double)1/(double)n;
+            for(i=0; i<=n-1; i++)
+            {
+                s = x[i];
+                samex = samex && (double)(s)==(double)(x0);
+                xmean = xmean+s*v;
+                s = y[i];
+                samey = samey && (double)(s)==(double)(y0);
+                ymean = ymean+s*v;
+            }
+            if( samex || samey )
+            {
+                result = 0;
+                return result;
+            }
+            
+            //
+            // covariance
+            //
+            v = (double)1/(double)(n-1);
+            result = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                result = result+v*(x[i]-xmean)*(y[i]-ymean);
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        Pearson product-moment correlation coefficient
+
+        Input parameters:
+            X       -   sample 1 (array indexes: [0..N-1])
+            Y       -   sample 2 (array indexes: [0..N-1])
+            N       -   N>=0, sample size:
+                        * if given, only N leading elements of X/Y are processed
+                        * if not given, automatically determined from input sizes
+
+        Result:
+            Pearson product-moment correlation coefficient
+            (zero for N=0 or N=1)
+
+          -- ALGLIB --
+             Copyright 28.10.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static double pearsoncorr2(double[] x,
+            double[] y,
+            int n)
+        {
+            double result = 0;
+            int i = 0;
+            double xmean = 0;
+            double ymean = 0;
+            double v = 0;
+            double x0 = 0;
+            double y0 = 0;
+            double s = 0;
+            bool samex = new bool();
+            bool samey = new bool();
+            double xv = 0;
+            double yv = 0;
+            double t1 = 0;
+            double t2 = 0;
+
+            alglib.ap.assert(n>=0, "PearsonCorr2: N<0");
+            alglib.ap.assert(alglib.ap.len(x)>=n, "PearsonCorr2: Length(X)<N!");
+            alglib.ap.assert(alglib.ap.len(y)>=n, "PearsonCorr2: Length(Y)<N!");
+            alglib.ap.assert(apserv.isfinitevector(x, n), "PearsonCorr2: X is not finite vector");
+            alglib.ap.assert(apserv.isfinitevector(y, n), "PearsonCorr2: Y is not finite vector");
+            
+            //
+            // Special case
+            //
+            if( n<=1 )
+            {
+                result = 0;
+                return result;
+            }
+            
+            //
+            // Calculate mean.
+            //
+            //
+            // Additonally we calculate SameX and SameY -
+            // flag variables which are set to True when
+            // all X[] (or Y[]) contain exactly same value.
+            //
+            // If at least one of them is True, we return zero
+            // (othwerwise we risk to get nonzero correlation
+            // because of roundoff).
+            //
+            xmean = 0;
+            ymean = 0;
+            samex = true;
+            samey = true;
+            x0 = x[0];
+            y0 = y[0];
+            v = (double)1/(double)n;
+            for(i=0; i<=n-1; i++)
+            {
+                s = x[i];
+                samex = samex && (double)(s)==(double)(x0);
+                xmean = xmean+s*v;
+                s = y[i];
+                samey = samey && (double)(s)==(double)(y0);
+                ymean = ymean+s*v;
+            }
+            if( samex || samey )
+            {
+                result = 0;
+                return result;
+            }
+            
+            //
+            // numerator and denominator
+            //
+            s = 0;
+            xv = 0;
+            yv = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                t1 = x[i]-xmean;
+                t2 = y[i]-ymean;
+                xv = xv+math.sqr(t1);
+                yv = yv+math.sqr(t2);
+                s = s+t1*t2;
+            }
+            if( (double)(xv)==(double)(0) || (double)(yv)==(double)(0) )
+            {
+                result = 0;
+            }
+            else
+            {
+                result = s/(Math.Sqrt(xv)*Math.Sqrt(yv));
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        Spearman's rank correlation coefficient
+
+        Input parameters:
+            X       -   sample 1 (array indexes: [0..N-1])
+            Y       -   sample 2 (array indexes: [0..N-1])
+            N       -   N>=0, sample size:
+                        * if given, only N leading elements of X/Y are processed
+                        * if not given, automatically determined from input sizes
+
+        Result:
+            Spearman's rank correlation coefficient
+            (zero for N=0 or N=1)
+
+          -- ALGLIB --
+             Copyright 09.04.2007 by Bochkanov Sergey
+        *************************************************************************/
+        public static double spearmancorr2(double[] x,
+            double[] y,
+            int n)
+        {
+            double result = 0;
+            apserv.apbuffers buf = new apserv.apbuffers();
+
+            x = (double[])x.Clone();
+            y = (double[])y.Clone();
+
+            alglib.ap.assert(n>=0, "SpearmanCorr2: N<0");
+            alglib.ap.assert(alglib.ap.len(x)>=n, "SpearmanCorr2: Length(X)<N!");
+            alglib.ap.assert(alglib.ap.len(y)>=n, "SpearmanCorr2: Length(Y)<N!");
+            alglib.ap.assert(apserv.isfinitevector(x, n), "SpearmanCorr2: X is not finite vector");
+            alglib.ap.assert(apserv.isfinitevector(y, n), "SpearmanCorr2: Y is not finite vector");
+            
+            //
+            // Special case
+            //
+            if( n<=1 )
+            {
+                result = 0;
+                return result;
+            }
+            basicstatops.rankx(ref x, n, buf);
+            basicstatops.rankx(ref y, n, buf);
+            result = pearsoncorr2(x, y, n);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Covariance matrix
+
+        INPUT PARAMETERS:
+            X   -   array[N,M], sample matrix:
+                    * J-th column corresponds to J-th variable
+                    * I-th row corresponds to I-th observation
+            N   -   N>=0, number of observations:
+                    * if given, only leading N rows of X are used
+                    * if not given, automatically determined from input size
+            M   -   M>0, number of variables:
+                    * if given, only leading M columns of X are used
+                    * if not given, automatically determined from input size
+
+        OUTPUT PARAMETERS:
+            C   -   array[M,M], covariance matrix (zero if N=0 or N=1)
+
+          -- ALGLIB --
+             Copyright 28.10.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void covm(double[,] x,
+            int n,
+            int m,
+            ref double[,] c)
+        {
+            int i = 0;
+            int j = 0;
+            double v = 0;
+            double[] t = new double[0];
+            double[] x0 = new double[0];
+            bool[] same = new bool[0];
+            int i_ = 0;
+
+            x = (double[,])x.Clone();
+            c = new double[0,0];
+
+            alglib.ap.assert(n>=0, "CovM: N<0");
+            alglib.ap.assert(m>=1, "CovM: M<1");
+            alglib.ap.assert(alglib.ap.rows(x)>=n, "CovM: Rows(X)<N!");
+            alglib.ap.assert(alglib.ap.cols(x)>=m || n==0, "CovM: Cols(X)<M!");
+            alglib.ap.assert(apserv.apservisfinitematrix(x, n, m), "CovM: X contains infinite/NAN elements");
+            
+            //
+            // N<=1, return zero
+            //
+            if( n<=1 )
+            {
+                c = new double[m, m];
+                for(i=0; i<=m-1; i++)
+                {
+                    for(j=0; j<=m-1; j++)
+                    {
+                        c[i,j] = 0;
+                    }
+                }
+                return;
+            }
+            
+            //
+            // Calculate means,
+            // check for constant columns
+            //
+            t = new double[m];
+            x0 = new double[m];
+            same = new bool[m];
+            c = new double[m, m];
+            for(i=0; i<=m-1; i++)
+            {
+                t[i] = 0;
+                same[i] = true;
+            }
+            for(i_=0; i_<=m-1;i_++)
+            {
+                x0[i_] = x[0,i_];
+            }
+            v = (double)1/(double)n;
+            for(i=0; i<=n-1; i++)
+            {
+                for(i_=0; i_<=m-1;i_++)
+                {
+                    t[i_] = t[i_] + v*x[i,i_];
+                }
+                for(j=0; j<=m-1; j++)
+                {
+                    same[j] = same[j] && (double)(x[i,j])==(double)(x0[j]);
+                }
+            }
+            
+            //
+            // * center variables;
+            // * if we have constant columns, these columns are
+            //   artificially zeroed (they must be zero in exact arithmetics,
+            //   but unfortunately floating point ops are not exact).
+            // * calculate upper half of symmetric covariance matrix
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                for(i_=0; i_<=m-1;i_++)
+                {
+                    x[i,i_] = x[i,i_] - t[i_];
+                }
+                for(j=0; j<=m-1; j++)
+                {
+                    if( same[j] )
+                    {
+                        x[i,j] = 0;
+                    }
+                }
+            }
+            ablas.rmatrixsyrk(m, n, (double)1/(double)(n-1), x, 0, 0, 1, 0.0, ref c, 0, 0, true);
+            
+            //
+            // force symmetricity
+            //
+            for(i=0; i<=m-2; i++)
+            {
+                for(i_=i+1; i_<=m-1;i_++)
+                {
+                    c[i_,i] = c[i,i_];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Pearson product-moment correlation matrix
+
+        INPUT PARAMETERS:
+            X   -   array[N,M], sample matrix:
+                    * J-th column corresponds to J-th variable
+                    * I-th row corresponds to I-th observation
+            N   -   N>=0, number of observations:
+                    * if given, only leading N rows of X are used
+                    * if not given, automatically determined from input size
+            M   -   M>0, number of variables:
+                    * if given, only leading M columns of X are used
+                    * if not given, automatically determined from input size
+
+        OUTPUT PARAMETERS:
+            C   -   array[M,M], correlation matrix (zero if N=0 or N=1)
+
+          -- ALGLIB --
+             Copyright 28.10.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void pearsoncorrm(double[,] x,
+            int n,
+            int m,
+            ref double[,] c)
+        {
+            double[] t = new double[0];
+            int i = 0;
+            int j = 0;
+
+            c = new double[0,0];
+
+            alglib.ap.assert(n>=0, "PearsonCorrM: N<0");
+            alglib.ap.assert(m>=1, "PearsonCorrM: M<1");
+            alglib.ap.assert(alglib.ap.rows(x)>=n, "PearsonCorrM: Rows(X)<N!");
+            alglib.ap.assert(alglib.ap.cols(x)>=m || n==0, "PearsonCorrM: Cols(X)<M!");
+            alglib.ap.assert(apserv.apservisfinitematrix(x, n, m), "PearsonCorrM: X contains infinite/NAN elements");
+            t = new double[m];
+            covm(x, n, m, ref c);
+            for(i=0; i<=m-1; i++)
+            {
+                t[i] = Math.Sqrt(c[i,i]);
+            }
+            for(i=0; i<=m-1; i++)
+            {
+                for(j=0; j<=m-1; j++)
+                {
+                    if( (double)(t[i])!=(double)(0) && (double)(t[j])!=(double)(0) )
+                    {
+                        c[i,j] = c[i,j]/(t[i]*t[j]);
+                    }
+                    else
+                    {
+                        c[i,j] = 0.0;
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Spearman's rank correlation matrix
+
+        INPUT PARAMETERS:
+            X   -   array[N,M], sample matrix:
+                    * J-th column corresponds to J-th variable
+                    * I-th row corresponds to I-th observation
+            N   -   N>=0, number of observations:
+                    * if given, only leading N rows of X are used
+                    * if not given, automatically determined from input size
+            M   -   M>0, number of variables:
+                    * if given, only leading M columns of X are used
+                    * if not given, automatically determined from input size
+
+        OUTPUT PARAMETERS:
+            C   -   array[M,M], correlation matrix (zero if N=0 or N=1)
+
+          -- ALGLIB --
+             Copyright 28.10.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spearmancorrm(double[,] x,
+            int n,
+            int m,
+            ref double[,] c)
+        {
+            int i = 0;
+            int j = 0;
+            apserv.apbuffers buf = new apserv.apbuffers();
+            double[] t = new double[0];
+            double v = 0;
+            double[] x0 = new double[0];
+            bool[] same = new bool[0];
+            int i_ = 0;
+
+            x = (double[,])x.Clone();
+            c = new double[0,0];
+
+            alglib.ap.assert(n>=0, "SpearmanCorrM: N<0");
+            alglib.ap.assert(m>=1, "SpearmanCorrM: M<1");
+            alglib.ap.assert(alglib.ap.rows(x)>=n, "SpearmanCorrM: Rows(X)<N!");
+            alglib.ap.assert(alglib.ap.cols(x)>=m || n==0, "SpearmanCorrM: Cols(X)<M!");
+            alglib.ap.assert(apserv.apservisfinitematrix(x, n, m), "SpearmanCorrM: X contains infinite/NAN elements");
+            
+            //
+            // N<=1, return zero
+            //
+            if( n<=1 )
+            {
+                c = new double[m, m];
+                for(i=0; i<=m-1; i++)
+                {
+                    for(j=0; j<=m-1; j++)
+                    {
+                        c[i,j] = 0;
+                    }
+                }
+                return;
+            }
+            
+            //
+            // Allocate
+            //
+            t = new double[Math.Max(n, m)];
+            x0 = new double[m];
+            same = new bool[m];
+            c = new double[m, m];
+            
+            //
+            // Replace data with ranks
+            //
+            for(j=0; j<=m-1; j++)
+            {
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    t[i_] = x[i_,j];
+                }
+                basicstatops.rankx(ref t, n, buf);
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    x[i_,j] = t[i_];
+                }
+            }
+            
+            //
+            // Calculate means,
+            // check for constant columns
+            //
+            for(i=0; i<=m-1; i++)
+            {
+                t[i] = 0;
+                same[i] = true;
+            }
+            for(i_=0; i_<=m-1;i_++)
+            {
+                x0[i_] = x[0,i_];
+            }
+            v = (double)1/(double)n;
+            for(i=0; i<=n-1; i++)
+            {
+                for(i_=0; i_<=m-1;i_++)
+                {
+                    t[i_] = t[i_] + v*x[i,i_];
+                }
+                for(j=0; j<=m-1; j++)
+                {
+                    same[j] = same[j] && (double)(x[i,j])==(double)(x0[j]);
+                }
+            }
+            
+            //
+            // * center variables;
+            // * if we have constant columns, these columns are
+            //   artificialy zeroed (they must be zero in exact arithmetics,
+            //   but unfortunately floating point is not exact).
+            // * calculate upper half of symmetric covariance matrix
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                for(i_=0; i_<=m-1;i_++)
+                {
+                    x[i,i_] = x[i,i_] - t[i_];
+                }
+                for(j=0; j<=m-1; j++)
+                {
+                    if( same[j] )
+                    {
+                        x[i,j] = 0;
+                    }
+                }
+            }
+            ablas.rmatrixsyrk(m, n, (double)1/(double)(n-1), x, 0, 0, 1, 0.0, ref c, 0, 0, true);
+            
+            //
+            // force symmetricity
+            //
+            for(i=0; i<=m-2; i++)
+            {
+                for(i_=i+1; i_<=m-1;i_++)
+                {
+                    c[i_,i] = c[i,i_];
+                }
+            }
+            
+            //
+            // Calculate Pearson coefficients
+            //
+            for(i=0; i<=m-1; i++)
+            {
+                t[i] = Math.Sqrt(c[i,i]);
+            }
+            for(i=0; i<=m-1; i++)
+            {
+                for(j=0; j<=m-1; j++)
+                {
+                    if( (double)(t[i])!=(double)(0) && (double)(t[j])!=(double)(0) )
+                    {
+                        c[i,j] = c[i,j]/(t[i]*t[j]);
+                    }
+                    else
+                    {
+                        c[i,j] = 0.0;
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Cross-covariance matrix
+
+        INPUT PARAMETERS:
+            X   -   array[N,M1], sample matrix:
+                    * J-th column corresponds to J-th variable
+                    * I-th row corresponds to I-th observation
+            Y   -   array[N,M2], sample matrix:
+                    * J-th column corresponds to J-th variable
+                    * I-th row corresponds to I-th observation
+            N   -   N>=0, number of observations:
+                    * if given, only leading N rows of X/Y are used
+                    * if not given, automatically determined from input sizes
+            M1  -   M1>0, number of variables in X:
+                    * if given, only leading M1 columns of X are used
+                    * if not given, automatically determined from input size
+            M2  -   M2>0, number of variables in Y:
+                    * if given, only leading M1 columns of X are used
+                    * if not given, automatically determined from input size
+
+        OUTPUT PARAMETERS:
+            C   -   array[M1,M2], cross-covariance matrix (zero if N=0 or N=1)
+
+          -- ALGLIB --
+             Copyright 28.10.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void covm2(double[,] x,
+            double[,] y,
+            int n,
+            int m1,
+            int m2,
+            ref double[,] c)
+        {
+            int i = 0;
+            int j = 0;
+            double v = 0;
+            double[] t = new double[0];
+            double[] x0 = new double[0];
+            double[] y0 = new double[0];
+            bool[] samex = new bool[0];
+            bool[] samey = new bool[0];
+            int i_ = 0;
+
+            x = (double[,])x.Clone();
+            y = (double[,])y.Clone();
+            c = new double[0,0];
+
+            alglib.ap.assert(n>=0, "CovM2: N<0");
+            alglib.ap.assert(m1>=1, "CovM2: M1<1");
+            alglib.ap.assert(m2>=1, "CovM2: M2<1");
+            alglib.ap.assert(alglib.ap.rows(x)>=n, "CovM2: Rows(X)<N!");
+            alglib.ap.assert(alglib.ap.cols(x)>=m1 || n==0, "CovM2: Cols(X)<M1!");
+            alglib.ap.assert(apserv.apservisfinitematrix(x, n, m1), "CovM2: X contains infinite/NAN elements");
+            alglib.ap.assert(alglib.ap.rows(y)>=n, "CovM2: Rows(Y)<N!");
+            alglib.ap.assert(alglib.ap.cols(y)>=m2 || n==0, "CovM2: Cols(Y)<M2!");
+            alglib.ap.assert(apserv.apservisfinitematrix(y, n, m2), "CovM2: X contains infinite/NAN elements");
+            
+            //
+            // N<=1, return zero
+            //
+            if( n<=1 )
+            {
+                c = new double[m1, m2];
+                for(i=0; i<=m1-1; i++)
+                {
+                    for(j=0; j<=m2-1; j++)
+                    {
+                        c[i,j] = 0;
+                    }
+                }
+                return;
+            }
+            
+            //
+            // Allocate
+            //
+            t = new double[Math.Max(m1, m2)];
+            x0 = new double[m1];
+            y0 = new double[m2];
+            samex = new bool[m1];
+            samey = new bool[m2];
+            c = new double[m1, m2];
+            
+            //
+            // * calculate means of X
+            // * center X
+            // * if we have constant columns, these columns are
+            //   artificially zeroed (they must be zero in exact arithmetics,
+            //   but unfortunately floating point ops are not exact).
+            //
+            for(i=0; i<=m1-1; i++)
+            {
+                t[i] = 0;
+                samex[i] = true;
+            }
+            for(i_=0; i_<=m1-1;i_++)
+            {
+                x0[i_] = x[0,i_];
+            }
+            v = (double)1/(double)n;
+            for(i=0; i<=n-1; i++)
+            {
+                for(i_=0; i_<=m1-1;i_++)
+                {
+                    t[i_] = t[i_] + v*x[i,i_];
+                }
+                for(j=0; j<=m1-1; j++)
+                {
+                    samex[j] = samex[j] && (double)(x[i,j])==(double)(x0[j]);
+                }
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                for(i_=0; i_<=m1-1;i_++)
+                {
+                    x[i,i_] = x[i,i_] - t[i_];
+                }
+                for(j=0; j<=m1-1; j++)
+                {
+                    if( samex[j] )
+                    {
+                        x[i,j] = 0;
+                    }
+                }
+            }
+            
+            //
+            // Repeat same steps for Y
+            //
+            for(i=0; i<=m2-1; i++)
+            {
+                t[i] = 0;
+                samey[i] = true;
+            }
+            for(i_=0; i_<=m2-1;i_++)
+            {
+                y0[i_] = y[0,i_];
+            }
+            v = (double)1/(double)n;
+            for(i=0; i<=n-1; i++)
+            {
+                for(i_=0; i_<=m2-1;i_++)
+                {
+                    t[i_] = t[i_] + v*y[i,i_];
+                }
+                for(j=0; j<=m2-1; j++)
+                {
+                    samey[j] = samey[j] && (double)(y[i,j])==(double)(y0[j]);
+                }
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                for(i_=0; i_<=m2-1;i_++)
+                {
+                    y[i,i_] = y[i,i_] - t[i_];
+                }
+                for(j=0; j<=m2-1; j++)
+                {
+                    if( samey[j] )
+                    {
+                        y[i,j] = 0;
+                    }
+                }
+            }
+            
+            //
+            // calculate cross-covariance matrix
+            //
+            ablas.rmatrixgemm(m1, m2, n, (double)1/(double)(n-1), x, 0, 0, 1, y, 0, 0, 0, 0.0, ref c, 0, 0);
+        }
+
+
+        /*************************************************************************
+        Pearson product-moment cross-correlation matrix
+
+        INPUT PARAMETERS:
+            X   -   array[N,M1], sample matrix:
+                    * J-th column corresponds to J-th variable
+                    * I-th row corresponds to I-th observation
+            Y   -   array[N,M2], sample matrix:
+                    * J-th column corresponds to J-th variable
+                    * I-th row corresponds to I-th observation
+            N   -   N>=0, number of observations:
+                    * if given, only leading N rows of X/Y are used
+                    * if not given, automatically determined from input sizes
+            M1  -   M1>0, number of variables in X:
+                    * if given, only leading M1 columns of X are used
+                    * if not given, automatically determined from input size
+            M2  -   M2>0, number of variables in Y:
+                    * if given, only leading M1 columns of X are used
+                    * if not given, automatically determined from input size
+
+        OUTPUT PARAMETERS:
+            C   -   array[M1,M2], cross-correlation matrix (zero if N=0 or N=1)
+
+          -- ALGLIB --
+             Copyright 28.10.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void pearsoncorrm2(double[,] x,
+            double[,] y,
+            int n,
+            int m1,
+            int m2,
+            ref double[,] c)
+        {
+            int i = 0;
+            int j = 0;
+            double v = 0;
+            double[] t = new double[0];
+            double[] x0 = new double[0];
+            double[] y0 = new double[0];
+            double[] sx = new double[0];
+            double[] sy = new double[0];
+            bool[] samex = new bool[0];
+            bool[] samey = new bool[0];
+            int i_ = 0;
+
+            x = (double[,])x.Clone();
+            y = (double[,])y.Clone();
+            c = new double[0,0];
+
+            alglib.ap.assert(n>=0, "PearsonCorrM2: N<0");
+            alglib.ap.assert(m1>=1, "PearsonCorrM2: M1<1");
+            alglib.ap.assert(m2>=1, "PearsonCorrM2: M2<1");
+            alglib.ap.assert(alglib.ap.rows(x)>=n, "PearsonCorrM2: Rows(X)<N!");
+            alglib.ap.assert(alglib.ap.cols(x)>=m1 || n==0, "PearsonCorrM2: Cols(X)<M1!");
+            alglib.ap.assert(apserv.apservisfinitematrix(x, n, m1), "PearsonCorrM2: X contains infinite/NAN elements");
+            alglib.ap.assert(alglib.ap.rows(y)>=n, "PearsonCorrM2: Rows(Y)<N!");
+            alglib.ap.assert(alglib.ap.cols(y)>=m2 || n==0, "PearsonCorrM2: Cols(Y)<M2!");
+            alglib.ap.assert(apserv.apservisfinitematrix(y, n, m2), "PearsonCorrM2: X contains infinite/NAN elements");
+            
+            //
+            // N<=1, return zero
+            //
+            if( n<=1 )
+            {
+                c = new double[m1, m2];
+                for(i=0; i<=m1-1; i++)
+                {
+                    for(j=0; j<=m2-1; j++)
+                    {
+                        c[i,j] = 0;
+                    }
+                }
+                return;
+            }
+            
+            //
+            // Allocate
+            //
+            t = new double[Math.Max(m1, m2)];
+            x0 = new double[m1];
+            y0 = new double[m2];
+            sx = new double[m1];
+            sy = new double[m2];
+            samex = new bool[m1];
+            samey = new bool[m2];
+            c = new double[m1, m2];
+            
+            //
+            // * calculate means of X
+            // * center X
+            // * if we have constant columns, these columns are
+            //   artificially zeroed (they must be zero in exact arithmetics,
+            //   but unfortunately floating point ops are not exact).
+            // * calculate column variances
+            //
+            for(i=0; i<=m1-1; i++)
+            {
+                t[i] = 0;
+                samex[i] = true;
+                sx[i] = 0;
+            }
+            for(i_=0; i_<=m1-1;i_++)
+            {
+                x0[i_] = x[0,i_];
+            }
+            v = (double)1/(double)n;
+            for(i=0; i<=n-1; i++)
+            {
+                for(i_=0; i_<=m1-1;i_++)
+                {
+                    t[i_] = t[i_] + v*x[i,i_];
+                }
+                for(j=0; j<=m1-1; j++)
+                {
+                    samex[j] = samex[j] && (double)(x[i,j])==(double)(x0[j]);
+                }
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                for(i_=0; i_<=m1-1;i_++)
+                {
+                    x[i,i_] = x[i,i_] - t[i_];
+                }
+                for(j=0; j<=m1-1; j++)
+                {
+                    if( samex[j] )
+                    {
+                        x[i,j] = 0;
+                    }
+                    sx[j] = sx[j]+x[i,j]*x[i,j];
+                }
+            }
+            for(j=0; j<=m1-1; j++)
+            {
+                sx[j] = Math.Sqrt(sx[j]/(n-1));
+            }
+            
+            //
+            // Repeat same steps for Y
+            //
+            for(i=0; i<=m2-1; i++)
+            {
+                t[i] = 0;
+                samey[i] = true;
+                sy[i] = 0;
+            }
+            for(i_=0; i_<=m2-1;i_++)
+            {
+                y0[i_] = y[0,i_];
+            }
+            v = (double)1/(double)n;
+            for(i=0; i<=n-1; i++)
+            {
+                for(i_=0; i_<=m2-1;i_++)
+                {
+                    t[i_] = t[i_] + v*y[i,i_];
+                }
+                for(j=0; j<=m2-1; j++)
+                {
+                    samey[j] = samey[j] && (double)(y[i,j])==(double)(y0[j]);
+                }
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                for(i_=0; i_<=m2-1;i_++)
+                {
+                    y[i,i_] = y[i,i_] - t[i_];
+                }
+                for(j=0; j<=m2-1; j++)
+                {
+                    if( samey[j] )
+                    {
+                        y[i,j] = 0;
+                    }
+                    sy[j] = sy[j]+y[i,j]*y[i,j];
+                }
+            }
+            for(j=0; j<=m2-1; j++)
+            {
+                sy[j] = Math.Sqrt(sy[j]/(n-1));
+            }
+            
+            //
+            // calculate cross-covariance matrix
+            //
+            ablas.rmatrixgemm(m1, m2, n, (double)1/(double)(n-1), x, 0, 0, 1, y, 0, 0, 0, 0.0, ref c, 0, 0);
+            
+            //
+            // Divide by standard deviations
+            //
+            for(i=0; i<=m1-1; i++)
+            {
+                for(j=0; j<=m2-1; j++)
+                {
+                    if( (double)(sx[i])!=(double)(0) && (double)(sy[j])!=(double)(0) )
+                    {
+                        c[i,j] = c[i,j]/(sx[i]*sy[j]);
+                    }
+                    else
+                    {
+                        c[i,j] = 0;
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Spearman's rank cross-correlation matrix
+
+        INPUT PARAMETERS:
+            X   -   array[N,M1], sample matrix:
+                    * J-th column corresponds to J-th variable
+                    * I-th row corresponds to I-th observation
+            Y   -   array[N,M2], sample matrix:
+                    * J-th column corresponds to J-th variable
+                    * I-th row corresponds to I-th observation
+            N   -   N>=0, number of observations:
+                    * if given, only leading N rows of X/Y are used
+                    * if not given, automatically determined from input sizes
+            M1  -   M1>0, number of variables in X:
+                    * if given, only leading M1 columns of X are used
+                    * if not given, automatically determined from input size
+            M2  -   M2>0, number of variables in Y:
+                    * if given, only leading M1 columns of X are used
+                    * if not given, automatically determined from input size
+
+        OUTPUT PARAMETERS:
+            C   -   array[M1,M2], cross-correlation matrix (zero if N=0 or N=1)
+
+          -- ALGLIB --
+             Copyright 28.10.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spearmancorrm2(double[,] x,
+            double[,] y,
+            int n,
+            int m1,
+            int m2,
+            ref double[,] c)
+        {
+            int i = 0;
+            int j = 0;
+            double v = 0;
+            double[] t = new double[0];
+            double[] x0 = new double[0];
+            double[] y0 = new double[0];
+            double[] sx = new double[0];
+            double[] sy = new double[0];
+            bool[] samex = new bool[0];
+            bool[] samey = new bool[0];
+            apserv.apbuffers buf = new apserv.apbuffers();
+            int i_ = 0;
+
+            x = (double[,])x.Clone();
+            y = (double[,])y.Clone();
+            c = new double[0,0];
+
+            alglib.ap.assert(n>=0, "SpearmanCorrM2: N<0");
+            alglib.ap.assert(m1>=1, "SpearmanCorrM2: M1<1");
+            alglib.ap.assert(m2>=1, "SpearmanCorrM2: M2<1");
+            alglib.ap.assert(alglib.ap.rows(x)>=n, "SpearmanCorrM2: Rows(X)<N!");
+            alglib.ap.assert(alglib.ap.cols(x)>=m1 || n==0, "SpearmanCorrM2: Cols(X)<M1!");
+            alglib.ap.assert(apserv.apservisfinitematrix(x, n, m1), "SpearmanCorrM2: X contains infinite/NAN elements");
+            alglib.ap.assert(alglib.ap.rows(y)>=n, "SpearmanCorrM2: Rows(Y)<N!");
+            alglib.ap.assert(alglib.ap.cols(y)>=m2 || n==0, "SpearmanCorrM2: Cols(Y)<M2!");
+            alglib.ap.assert(apserv.apservisfinitematrix(y, n, m2), "SpearmanCorrM2: X contains infinite/NAN elements");
+            
+            //
+            // N<=1, return zero
+            //
+            if( n<=1 )
+            {
+                c = new double[m1, m2];
+                for(i=0; i<=m1-1; i++)
+                {
+                    for(j=0; j<=m2-1; j++)
+                    {
+                        c[i,j] = 0;
+                    }
+                }
+                return;
+            }
+            
+            //
+            // Allocate
+            //
+            t = new double[Math.Max(Math.Max(m1, m2), n)];
+            x0 = new double[m1];
+            y0 = new double[m2];
+            sx = new double[m1];
+            sy = new double[m2];
+            samex = new bool[m1];
+            samey = new bool[m2];
+            c = new double[m1, m2];
+            
+            //
+            // Replace data with ranks
+            //
+            for(j=0; j<=m1-1; j++)
+            {
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    t[i_] = x[i_,j];
+                }
+                basicstatops.rankx(ref t, n, buf);
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    x[i_,j] = t[i_];
+                }
+            }
+            for(j=0; j<=m2-1; j++)
+            {
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    t[i_] = y[i_,j];
+                }
+                basicstatops.rankx(ref t, n, buf);
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    y[i_,j] = t[i_];
+                }
+            }
+            
+            //
+            // * calculate means of X
+            // * center X
+            // * if we have constant columns, these columns are
+            //   artificially zeroed (they must be zero in exact arithmetics,
+            //   but unfortunately floating point ops are not exact).
+            // * calculate column variances
+            //
+            for(i=0; i<=m1-1; i++)
+            {
+                t[i] = 0;
+                samex[i] = true;
+                sx[i] = 0;
+            }
+            for(i_=0; i_<=m1-1;i_++)
+            {
+                x0[i_] = x[0,i_];
+            }
+            v = (double)1/(double)n;
+            for(i=0; i<=n-1; i++)
+            {
+                for(i_=0; i_<=m1-1;i_++)
+                {
+                    t[i_] = t[i_] + v*x[i,i_];
+                }
+                for(j=0; j<=m1-1; j++)
+                {
+                    samex[j] = samex[j] && (double)(x[i,j])==(double)(x0[j]);
+                }
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                for(i_=0; i_<=m1-1;i_++)
+                {
+                    x[i,i_] = x[i,i_] - t[i_];
+                }
+                for(j=0; j<=m1-1; j++)
+                {
+                    if( samex[j] )
+                    {
+                        x[i,j] = 0;
+                    }
+                    sx[j] = sx[j]+x[i,j]*x[i,j];
+                }
+            }
+            for(j=0; j<=m1-1; j++)
+            {
+                sx[j] = Math.Sqrt(sx[j]/(n-1));
+            }
+            
+            //
+            // Repeat same steps for Y
+            //
+            for(i=0; i<=m2-1; i++)
+            {
+                t[i] = 0;
+                samey[i] = true;
+                sy[i] = 0;
+            }
+            for(i_=0; i_<=m2-1;i_++)
+            {
+                y0[i_] = y[0,i_];
+            }
+            v = (double)1/(double)n;
+            for(i=0; i<=n-1; i++)
+            {
+                for(i_=0; i_<=m2-1;i_++)
+                {
+                    t[i_] = t[i_] + v*y[i,i_];
+                }
+                for(j=0; j<=m2-1; j++)
+                {
+                    samey[j] = samey[j] && (double)(y[i,j])==(double)(y0[j]);
+                }
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                for(i_=0; i_<=m2-1;i_++)
+                {
+                    y[i,i_] = y[i,i_] - t[i_];
+                }
+                for(j=0; j<=m2-1; j++)
+                {
+                    if( samey[j] )
+                    {
+                        y[i,j] = 0;
+                    }
+                    sy[j] = sy[j]+y[i,j]*y[i,j];
+                }
+            }
+            for(j=0; j<=m2-1; j++)
+            {
+                sy[j] = Math.Sqrt(sy[j]/(n-1));
+            }
+            
+            //
+            // calculate cross-covariance matrix
+            //
+            ablas.rmatrixgemm(m1, m2, n, (double)1/(double)(n-1), x, 0, 0, 1, y, 0, 0, 0, 0.0, ref c, 0, 0);
+            
+            //
+            // Divide by standard deviations
+            //
+            for(i=0; i<=m1-1; i++)
+            {
+                for(j=0; j<=m2-1; j++)
+                {
+                    if( (double)(sx[i])!=(double)(0) && (double)(sy[j])!=(double)(0) )
+                    {
+                        c[i,j] = c[i,j]/(sx[i]*sy[j]);
+                    }
+                    else
+                    {
+                        c[i,j] = 0;
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Obsolete function, we recommend to use PearsonCorr2().
+
+          -- ALGLIB --
+             Copyright 09.04.2007 by Bochkanov Sergey
+        *************************************************************************/
+        public static double pearsoncorrelation(double[] x,
+            double[] y,
+            int n)
+        {
+            double result = 0;
+
+            result = pearsoncorr2(x, y, n);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Obsolete function, we recommend to use SpearmanCorr2().
+
+            -- ALGLIB --
+            Copyright 09.04.2007 by Bochkanov Sergey
+        *************************************************************************/
+        public static double spearmanrankcorrelation(double[] x,
+            double[] y,
+            int n)
+        {
+            double result = 0;
+
+            result = spearmancorr2(x, y, n);
+            return result;
+        }
+
+
+    }
+    public class correlationtests
+    {
+        /*************************************************************************
+        Pearson's correlation coefficient significance test
+
+        This test checks hypotheses about whether X  and  Y  are  samples  of  two
+        continuous  distributions  having  zero  correlation  or   whether   their
+        correlation is non-zero.
+
+        The following tests are performed:
+            * two-tailed test (null hypothesis - X and Y have zero correlation)
+            * left-tailed test (null hypothesis - the correlation  coefficient  is
+              greater than or equal to 0)
+            * right-tailed test (null hypothesis - the correlation coefficient  is
+              less than or equal to 0).
+
+        Requirements:
+            * the number of elements in each sample is not less than 5
+            * normality of distributions of X and Y.
+
+        Input parameters:
+            R   -   Pearson's correlation coefficient for X and Y
+            N   -   number of elements in samples, N>=5.
+
+        Output parameters:
+            BothTails   -   p-value for two-tailed test.
+                            If BothTails is less than the given significance level
+                            the null hypothesis is rejected.
+            LeftTail    -   p-value for left-tailed test.
+                            If LeftTail is less than the given significance level,
+                            the null hypothesis is rejected.
+            RightTail   -   p-value for right-tailed test.
+                            If RightTail is less than the given significance level
+                            the null hypothesis is rejected.
+
+          -- ALGLIB --
+             Copyright 09.04.2007 by Bochkanov Sergey
+        *************************************************************************/
+        public static void pearsoncorrelationsignificance(double r,
+            int n,
+            ref double bothtails,
+            ref double lefttail,
+            ref double righttail)
+        {
+            double t = 0;
+            double p = 0;
+
+            bothtails = 0;
+            lefttail = 0;
+            righttail = 0;
+
+            
+            //
+            // Some special cases
+            //
+            if( (double)(r)>=(double)(1) )
+            {
+                bothtails = 0.0;
+                lefttail = 1.0;
+                righttail = 0.0;
+                return;
+            }
+            if( (double)(r)<=(double)(-1) )
+            {
+                bothtails = 0.0;
+                lefttail = 0.0;
+                righttail = 1.0;
+                return;
+            }
+            if( n<5 )
+            {
+                bothtails = 1.0;
+                lefttail = 1.0;
+                righttail = 1.0;
+                return;
+            }
+            
+            //
+            // General case
+            //
+            t = r*Math.Sqrt((n-2)/(1-math.sqr(r)));
+            p = studenttdistr.studenttdistribution(n-2, t);
+            bothtails = 2*Math.Min(p, 1-p);
+            lefttail = p;
+            righttail = 1-p;
+        }
+
+
+        /*************************************************************************
+        Spearman's rank correlation coefficient significance test
+
+        This test checks hypotheses about whether X  and  Y  are  samples  of  two
+        continuous  distributions  having  zero  correlation  or   whether   their
+        correlation is non-zero.
+
+        The following tests are performed:
+            * two-tailed test (null hypothesis - X and Y have zero correlation)
+            * left-tailed test (null hypothesis - the correlation  coefficient  is
+              greater than or equal to 0)
+            * right-tailed test (null hypothesis - the correlation coefficient  is
+              less than or equal to 0).
+
+        Requirements:
+            * the number of elements in each sample is not less than 5.
+
+        The test is non-parametric and doesn't require distributions X and Y to be
+        normal.
+
+        Input parameters:
+            R   -   Spearman's rank correlation coefficient for X and Y
+            N   -   number of elements in samples, N>=5.
+
+        Output parameters:
+            BothTails   -   p-value for two-tailed test.
+                            If BothTails is less than the given significance level
+                            the null hypothesis is rejected.
+            LeftTail    -   p-value for left-tailed test.
+                            If LeftTail is less than the given significance level,
+                            the null hypothesis is rejected.
+            RightTail   -   p-value for right-tailed test.
+                            If RightTail is less than the given significance level
+                            the null hypothesis is rejected.
+
+          -- ALGLIB --
+             Copyright 09.04.2007 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spearmanrankcorrelationsignificance(double r,
+            int n,
+            ref double bothtails,
+            ref double lefttail,
+            ref double righttail)
+        {
+            double t = 0;
+            double p = 0;
+
+            bothtails = 0;
+            lefttail = 0;
+            righttail = 0;
+
+            
+            //
+            // Special case
+            //
+            if( n<5 )
+            {
+                bothtails = 1.0;
+                lefttail = 1.0;
+                righttail = 1.0;
+                return;
+            }
+            
+            //
+            // General case
+            //
+            if( (double)(r)>=(double)(1) )
+            {
+                t = 1.0E10;
+            }
+            else
+            {
+                if( (double)(r)<=(double)(-1) )
+                {
+                    t = -1.0E10;
+                }
+                else
+                {
+                    t = r*Math.Sqrt((n-2)/(1-math.sqr(r)));
+                }
+            }
+            if( (double)(t)<(double)(0) )
+            {
+                p = spearmantail(t, n);
+                bothtails = 2*p;
+                lefttail = p;
+                righttail = 1-p;
+            }
+            else
+            {
+                p = spearmantail(-t, n);
+                bothtails = 2*p;
+                lefttail = 1-p;
+                righttail = p;
+            }
+        }
+
+
+        /*************************************************************************
+        Tail(S, 5)
+        *************************************************************************/
+        private static double spearmantail5(double s)
+        {
+            double result = 0;
+
+            if( (double)(s)<(double)(0.000e+00) )
+            {
+                result = studenttdistr.studenttdistribution(3, -s);
+                return result;
+            }
+            if( (double)(s)>=(double)(3.580e+00) )
+            {
+                result = 8.304e-03;
+                return result;
+            }
+            if( (double)(s)>=(double)(2.322e+00) )
+            {
+                result = 4.163e-02;
+                return result;
+            }
+            if( (double)(s)>=(double)(1.704e+00) )
+            {
+                result = 6.641e-02;
+                return result;
+            }
+            if( (double)(s)>=(double)(1.303e+00) )
+            {
+                result = 1.164e-01;
+                return result;
+            }
+            if( (double)(s)>=(double)(1.003e+00) )
+            {
+                result = 1.748e-01;
+                return result;
+            }
+            if( (double)(s)>=(double)(7.584e-01) )
+            {
+                result = 2.249e-01;
+                return result;
+            }
+            if( (double)(s)>=(double)(5.468e-01) )
+            {
+                result = 2.581e-01;
+                return result;
+            }
+            if( (double)(s)>=(double)(3.555e-01) )
+            {
+                result = 3.413e-01;
+                return result;
+            }
+            if( (double)(s)>=(double)(1.759e-01) )
+            {
+                result = 3.911e-01;
+                return result;
+            }
+            if( (double)(s)>=(double)(1.741e-03) )
+            {
+                result = 4.747e-01;
+                return result;
+            }
+            if( (double)(s)>=(double)(0.000e+00) )
+            {
+                result = 5.248e-01;
+                return result;
+            }
+            result = 0;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 6)
+        *************************************************************************/
+        private static double spearmantail6(double s)
+        {
+            double result = 0;
+
+            if( (double)(s)<(double)(1.001e+00) )
+            {
+                result = studenttdistr.studenttdistribution(4, -s);
+                return result;
+            }
+            if( (double)(s)>=(double)(5.663e+00) )
+            {
+                result = 1.366e-03;
+                return result;
+            }
+            if( (double)(s)>=(double)(3.834e+00) )
+            {
+                result = 8.350e-03;
+                return result;
+            }
+            if( (double)(s)>=(double)(2.968e+00) )
+            {
+                result = 1.668e-02;
+                return result;
+            }
+            if( (double)(s)>=(double)(2.430e+00) )
+            {
+                result = 2.921e-02;
+                return result;
+            }
+            if( (double)(s)>=(double)(2.045e+00) )
+            {
+                result = 5.144e-02;
+                return result;
+            }
+            if( (double)(s)>=(double)(1.747e+00) )
+            {
+                result = 6.797e-02;
+                return result;
+            }
+            if( (double)(s)>=(double)(1.502e+00) )
+            {
+                result = 8.752e-02;
+                return result;
+            }
+            if( (double)(s)>=(double)(1.295e+00) )
+            {
+                result = 1.210e-01;
+                return result;
+            }
+            if( (double)(s)>=(double)(1.113e+00) )
+            {
+                result = 1.487e-01;
+                return result;
+            }
+            if( (double)(s)>=(double)(1.001e+00) )
+            {
+                result = 1.780e-01;
+                return result;
+            }
+            result = 0;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 7)
+        *************************************************************************/
+        private static double spearmantail7(double s)
+        {
+            double result = 0;
+
+            if( (double)(s)<(double)(1.001e+00) )
+            {
+                result = studenttdistr.studenttdistribution(5, -s);
+                return result;
+            }
+            if( (double)(s)>=(double)(8.159e+00) )
+            {
+                result = 2.081e-04;
+                return result;
+            }
+            if( (double)(s)>=(double)(5.620e+00) )
+            {
+                result = 1.393e-03;
+                return result;
+            }
+            if( (double)(s)>=(double)(4.445e+00) )
+            {
+                result = 3.398e-03;
+                return result;
+            }
+            if( (double)(s)>=(double)(3.728e+00) )
+            {
+                result = 6.187e-03;
+                return result;
+            }
+            if( (double)(s)>=(double)(3.226e+00) )
+            {
+                result = 1.200e-02;
+                return result;
+            }
+            if( (double)(s)>=(double)(2.844e+00) )
+            {
+                result = 1.712e-02;
+                return result;
+            }
+            if( (double)(s)>=(double)(2.539e+00) )
+            {
+                result = 2.408e-02;
+                return result;
+            }
+            if( (double)(s)>=(double)(2.285e+00) )
+            {
+                result = 3.320e-02;
+                return result;
+            }
+            if( (double)(s)>=(double)(2.068e+00) )
+            {
+                result = 4.406e-02;
+                return result;
+            }
+            if( (double)(s)>=(double)(1.879e+00) )
+            {
+                result = 5.478e-02;
+                return result;
+            }
+            if( (double)(s)>=(double)(1.710e+00) )
+            {
+                result = 6.946e-02;
+                return result;
+            }
+            if( (double)(s)>=(double)(1.559e+00) )
+            {
+                result = 8.331e-02;
+                return result;
+            }
+            if( (double)(s)>=(double)(1.420e+00) )
+            {
+                result = 1.001e-01;
+                return result;
+            }
+            if( (double)(s)>=(double)(1.292e+00) )
+            {
+                result = 1.180e-01;
+                return result;
+            }
+            if( (double)(s)>=(double)(1.173e+00) )
+            {
+                result = 1.335e-01;
+                return result;
+            }
+            if( (double)(s)>=(double)(1.062e+00) )
+            {
+                result = 1.513e-01;
+                return result;
+            }
+            if( (double)(s)>=(double)(1.001e+00) )
+            {
+                result = 1.770e-01;
+                return result;
+            }
+            result = 0;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 8)
+        *************************************************************************/
+        private static double spearmantail8(double s)
+        {
+            double result = 0;
+
+            if( (double)(s)<(double)(2.001e+00) )
+            {
+                result = studenttdistr.studenttdistribution(6, -s);
+                return result;
+            }
+            if( (double)(s)>=(double)(1.103e+01) )
+            {
+                result = 2.194e-05;
+                return result;
+            }
+            if( (double)(s)>=(double)(7.685e+00) )
+            {
+                result = 2.008e-04;
+                return result;
+            }
+            if( (double)(s)>=(double)(6.143e+00) )
+            {
+                result = 5.686e-04;
+                return result;
+            }
+            if( (double)(s)>=(double)(5.213e+00) )
+            {
+                result = 1.138e-03;
+                return result;
+            }
+            if( (double)(s)>=(double)(4.567e+00) )
+            {
+                result = 2.310e-03;
+                return result;
+            }
+            if( (double)(s)>=(double)(4.081e+00) )
+            {
+                result = 3.634e-03;
+                return result;
+            }
+            if( (double)(s)>=(double)(3.697e+00) )
+            {
+                result = 5.369e-03;
+                return result;
+            }
+            if( (double)(s)>=(double)(3.381e+00) )
+            {
+                result = 7.708e-03;
+                return result;
+            }
+            if( (double)(s)>=(double)(3.114e+00) )
+            {
+                result = 1.087e-02;
+                return result;
+            }
+            if( (double)(s)>=(double)(2.884e+00) )
+            {
+                result = 1.397e-02;
+                return result;
+            }
+            if( (double)(s)>=(double)(2.682e+00) )
+            {
+                result = 1.838e-02;
+                return result;
+            }
+            if( (double)(s)>=(double)(2.502e+00) )
+            {
+                result = 2.288e-02;
+                return result;
+            }
+            if( (double)(s)>=(double)(2.340e+00) )
+            {
+                result = 2.883e-02;
+                return result;
+            }
+            if( (double)(s)>=(double)(2.192e+00) )
+            {
+                result = 3.469e-02;
+                return result;
+            }
+            if( (double)(s)>=(double)(2.057e+00) )
+            {
+                result = 4.144e-02;
+                return result;
+            }
+            if( (double)(s)>=(double)(2.001e+00) )
+            {
+                result = 4.804e-02;
+                return result;
+            }
+            result = 0;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 9)
+        *************************************************************************/
+        private static double spearmantail9(double s)
+        {
+            double result = 0;
+
+            if( (double)(s)<(double)(2.001e+00) )
+            {
+                result = studenttdistr.studenttdistribution(7, -s);
+                return result;
+            }
+            if( (double)(s)>=(double)(9.989e+00) )
+            {
+                result = 2.306e-05;
+                return result;
+            }
+            if( (double)(s)>=(double)(8.069e+00) )
+            {
+                result = 8.167e-05;
+                return result;
+            }
+            if( (double)(s)>=(double)(6.890e+00) )
+            {
+                result = 1.744e-04;
+                return result;
+            }
+            if( (double)(s)>=(double)(6.077e+00) )
+            {
+                result = 3.625e-04;
+                return result;
+            }
+            if( (double)(s)>=(double)(5.469e+00) )
+            {
+                result = 6.450e-04;
+                return result;
+            }
+            if( (double)(s)>=(double)(4.991e+00) )
+            {
+                result = 1.001e-03;
+                return result;
+            }
+            if( (double)(s)>=(double)(4.600e+00) )
+            {
+                result = 1.514e-03;
+                return result;
+            }
+            if( (double)(s)>=(double)(4.272e+00) )
+            {
+                result = 2.213e-03;
+                return result;
+            }
+            if( (double)(s)>=(double)(3.991e+00) )
+            {
+                result = 2.990e-03;
+                return result;
+            }
+            if( (double)(s)>=(double)(3.746e+00) )
+            {
+                result = 4.101e-03;
+                return result;
+            }
+            if( (double)(s)>=(double)(3.530e+00) )
+            {
+                result = 5.355e-03;
+                return result;
+            }
+            if( (double)(s)>=(double)(3.336e+00) )
+            {
+                result = 6.887e-03;
+                return result;
+            }
+            if( (double)(s)>=(double)(3.161e+00) )
+            {
+                result = 8.598e-03;
+                return result;
+            }
+            if( (double)(s)>=(double)(3.002e+00) )
+            {
+                result = 1.065e-02;
+                return result;
+            }
+            if( (double)(s)>=(double)(2.855e+00) )
+            {
+                result = 1.268e-02;
+                return result;
+            }
+            if( (double)(s)>=(double)(2.720e+00) )
+            {
+                result = 1.552e-02;
+                return result;
+            }
+            if( (double)(s)>=(double)(2.595e+00) )
+            {
+                result = 1.836e-02;
+                return result;
+            }
+            if( (double)(s)>=(double)(2.477e+00) )
+            {
+                result = 2.158e-02;
+                return result;
+            }
+            if( (double)(s)>=(double)(2.368e+00) )
+            {
+                result = 2.512e-02;
+                return result;
+            }
+            if( (double)(s)>=(double)(2.264e+00) )
+            {
+                result = 2.942e-02;
+                return result;
+            }
+            if( (double)(s)>=(double)(2.166e+00) )
+            {
+                result = 3.325e-02;
+                return result;
+            }
+            if( (double)(s)>=(double)(2.073e+00) )
+            {
+                result = 3.800e-02;
+                return result;
+            }
+            if( (double)(s)>=(double)(2.001e+00) )
+            {
+                result = 4.285e-02;
+                return result;
+            }
+            result = 0;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(T,N), accepts T<0
+        *************************************************************************/
+        private static double spearmantail(double t,
+            int n)
+        {
+            double result = 0;
+
+            if( n==5 )
+            {
+                result = spearmantail5(-t);
+                return result;
+            }
+            if( n==6 )
+            {
+                result = spearmantail6(-t);
+                return result;
+            }
+            if( n==7 )
+            {
+                result = spearmantail7(-t);
+                return result;
+            }
+            if( n==8 )
+            {
+                result = spearmantail8(-t);
+                return result;
+            }
+            if( n==9 )
+            {
+                result = spearmantail9(-t);
+                return result;
+            }
+            result = studenttdistr.studenttdistribution(n-2, t);
+            return result;
+        }
+
+
+    }
+    public class jarquebera
+    {
+        /*************************************************************************
+        Jarque-Bera test
+
+        This test checks hypotheses about the fact that a  given  sample  X  is  a
+        sample of normal random variable.
+
+        Requirements:
+            * the number of elements in the sample is not less than 5.
+
+        Input parameters:
+            X   -   sample. Array whose index goes from 0 to N-1.
+            N   -   size of the sample. N>=5
+
+        Output parameters:
+            BothTails   -   p-value for two-tailed test.
+                            If BothTails is less than the given significance level
+                            the null hypothesis is rejected.
+            LeftTail    -   p-value for left-tailed test.
+                            If LeftTail is less than the given significance level,
+                            the null hypothesis is rejected.
+            RightTail   -   p-value for right-tailed test.
+                            If RightTail is less than the given significance level
+                            the null hypothesis is rejected.
+
+        Accuracy of the approximation used (5<=N<=1951):
+
+        p-value  	    relative error (5<=N<=1951)
+        [1, 0.1]            < 1%
+        [0.1, 0.01]         < 2%
+        [0.01, 0.001]       < 6%
+        [0.001, 0]          wasn't measured
+
+        For N>1951 accuracy wasn't measured but it shouldn't be sharply  different
+        from table values.
+
+          -- ALGLIB --
+             Copyright 09.04.2007 by Bochkanov Sergey
+        *************************************************************************/
+        public static void jarqueberatest(double[] x,
+            int n,
+            ref double p)
+        {
+            double s = 0;
+
+            p = 0;
+
+            
+            //
+            // N is too small
+            //
+            if( n<5 )
+            {
+                p = 1.0;
+                return;
+            }
+            
+            //
+            // N is large enough
+            //
+            jarqueberastatistic(x, n, ref s);
+            p = jarqueberaapprox(n, s);
+        }
+
+
+        private static void jarqueberastatistic(double[] x,
+            int n,
+            ref double s)
+        {
+            int i = 0;
+            double v = 0;
+            double v1 = 0;
+            double v2 = 0;
+            double stddev = 0;
+            double mean = 0;
+            double variance = 0;
+            double skewness = 0;
+            double kurtosis = 0;
+
+            s = 0;
+
+            mean = 0;
+            variance = 0;
+            skewness = 0;
+            kurtosis = 0;
+            stddev = 0;
+            alglib.ap.assert(n>1);
+            
+            //
+            // Mean
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                mean = mean+x[i];
+            }
+            mean = mean/n;
+            
+            //
+            // Variance (using corrected two-pass algorithm)
+            //
+            if( n!=1 )
+            {
+                v1 = 0;
+                for(i=0; i<=n-1; i++)
+                {
+                    v1 = v1+math.sqr(x[i]-mean);
+                }
+                v2 = 0;
+                for(i=0; i<=n-1; i++)
+                {
+                    v2 = v2+(x[i]-mean);
+                }
+                v2 = math.sqr(v2)/n;
+                variance = (v1-v2)/(n-1);
+                if( (double)(variance)<(double)(0) )
+                {
+                    variance = 0;
+                }
+                stddev = Math.Sqrt(variance);
+            }
+            
+            //
+            // Skewness and kurtosis
+            //
+            if( (double)(stddev)!=(double)(0) )
+            {
+                for(i=0; i<=n-1; i++)
+                {
+                    v = (x[i]-mean)/stddev;
+                    v2 = math.sqr(v);
+                    skewness = skewness+v2*v;
+                    kurtosis = kurtosis+math.sqr(v2);
+                }
+                skewness = skewness/n;
+                kurtosis = kurtosis/n-3;
+            }
+            
+            //
+            // Statistic
+            //
+            s = (double)n/(double)6*(math.sqr(skewness)+math.sqr(kurtosis)/4);
+        }
+
+
+        private static double jarqueberaapprox(int n,
+            double s)
+        {
+            double result = 0;
+            double[] vx = new double[0];
+            double[] vy = new double[0];
+            double[,] ctbl = new double[0,0];
+            double t1 = 0;
+            double t2 = 0;
+            double t3 = 0;
+            double t = 0;
+            double f1 = 0;
+            double f2 = 0;
+            double f3 = 0;
+            double f12 = 0;
+            double f23 = 0;
+            double x = 0;
+
+            result = 1;
+            x = s;
+            if( n<5 )
+            {
+                return result;
+            }
+            
+            //
+            // N = 5..20 are tabulated
+            //
+            if( n>=5 && n<=20 )
+            {
+                if( n==5 )
+                {
+                    result = Math.Exp(jbtbl5(x));
+                }
+                if( n==6 )
+                {
+                    result = Math.Exp(jbtbl6(x));
+                }
+                if( n==7 )
+                {
+                    result = Math.Exp(jbtbl7(x));
+                }
+                if( n==8 )
+                {
+                    result = Math.Exp(jbtbl8(x));
+                }
+                if( n==9 )
+                {
+                    result = Math.Exp(jbtbl9(x));
+                }
+                if( n==10 )
+                {
+                    result = Math.Exp(jbtbl10(x));
+                }
+                if( n==11 )
+                {
+                    result = Math.Exp(jbtbl11(x));
+                }
+                if( n==12 )
+                {
+                    result = Math.Exp(jbtbl12(x));
+                }
+                if( n==13 )
+                {
+                    result = Math.Exp(jbtbl13(x));
+                }
+                if( n==14 )
+                {
+                    result = Math.Exp(jbtbl14(x));
+                }
+                if( n==15 )
+                {
+                    result = Math.Exp(jbtbl15(x));
+                }
+                if( n==16 )
+                {
+                    result = Math.Exp(jbtbl16(x));
+                }
+                if( n==17 )
+                {
+                    result = Math.Exp(jbtbl17(x));
+                }
+                if( n==18 )
+                {
+                    result = Math.Exp(jbtbl18(x));
+                }
+                if( n==19 )
+                {
+                    result = Math.Exp(jbtbl19(x));
+                }
+                if( n==20 )
+                {
+                    result = Math.Exp(jbtbl20(x));
+                }
+                return result;
+            }
+            
+            //
+            // N = 20, 30, 50 are tabulated.
+            // In-between values are interpolated
+            // using interpolating polynomial of the second degree.
+            //
+            if( n>20 && n<=50 )
+            {
+                t1 = -(1.0/20.0);
+                t2 = -(1.0/30.0);
+                t3 = -(1.0/50.0);
+                t = -(1.0/n);
+                f1 = jbtbl20(x);
+                f2 = jbtbl30(x);
+                f3 = jbtbl50(x);
+                f12 = ((t-t2)*f1+(t1-t)*f2)/(t1-t2);
+                f23 = ((t-t3)*f2+(t2-t)*f3)/(t2-t3);
+                result = ((t-t3)*f12+(t1-t)*f23)/(t1-t3);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                result = Math.Exp(result);
+                return result;
+            }
+            
+            //
+            // N = 50, 65, 100 are tabulated.
+            // In-between values are interpolated
+            // using interpolating polynomial of the second degree.
+            //
+            if( n>50 && n<=100 )
+            {
+                t1 = -(1.0/50.0);
+                t2 = -(1.0/65.0);
+                t3 = -(1.0/100.0);
+                t = -(1.0/n);
+                f1 = jbtbl50(x);
+                f2 = jbtbl65(x);
+                f3 = jbtbl100(x);
+                f12 = ((t-t2)*f1+(t1-t)*f2)/(t1-t2);
+                f23 = ((t-t3)*f2+(t2-t)*f3)/(t2-t3);
+                result = ((t-t3)*f12+(t1-t)*f23)/(t1-t3);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                result = Math.Exp(result);
+                return result;
+            }
+            
+            //
+            // N = 100, 130, 200 are tabulated.
+            // In-between values are interpolated
+            // using interpolating polynomial of the second degree.
+            //
+            if( n>100 && n<=200 )
+            {
+                t1 = -(1.0/100.0);
+                t2 = -(1.0/130.0);
+                t3 = -(1.0/200.0);
+                t = -(1.0/n);
+                f1 = jbtbl100(x);
+                f2 = jbtbl130(x);
+                f3 = jbtbl200(x);
+                f12 = ((t-t2)*f1+(t1-t)*f2)/(t1-t2);
+                f23 = ((t-t3)*f2+(t2-t)*f3)/(t2-t3);
+                result = ((t-t3)*f12+(t1-t)*f23)/(t1-t3);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                result = Math.Exp(result);
+                return result;
+            }
+            
+            //
+            // N = 200, 301, 501 are tabulated.
+            // In-between values are interpolated
+            // using interpolating polynomial of the second degree.
+            //
+            if( n>200 && n<=501 )
+            {
+                t1 = -(1.0/200.0);
+                t2 = -(1.0/301.0);
+                t3 = -(1.0/501.0);
+                t = -(1.0/n);
+                f1 = jbtbl200(x);
+                f2 = jbtbl301(x);
+                f3 = jbtbl501(x);
+                f12 = ((t-t2)*f1+(t1-t)*f2)/(t1-t2);
+                f23 = ((t-t3)*f2+(t2-t)*f3)/(t2-t3);
+                result = ((t-t3)*f12+(t1-t)*f23)/(t1-t3);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                result = Math.Exp(result);
+                return result;
+            }
+            
+            //
+            // N = 501, 701, 1401 are tabulated.
+            // In-between values are interpolated
+            // using interpolating polynomial of the second degree.
+            //
+            if( n>501 && n<=1401 )
+            {
+                t1 = -(1.0/501.0);
+                t2 = -(1.0/701.0);
+                t3 = -(1.0/1401.0);
+                t = -(1.0/n);
+                f1 = jbtbl501(x);
+                f2 = jbtbl701(x);
+                f3 = jbtbl1401(x);
+                f12 = ((t-t2)*f1+(t1-t)*f2)/(t1-t2);
+                f23 = ((t-t3)*f2+(t2-t)*f3)/(t2-t3);
+                result = ((t-t3)*f12+(t1-t)*f23)/(t1-t3);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                result = Math.Exp(result);
+                return result;
+            }
+            
+            //
+            // Asymptotic expansion
+            //
+            if( n>1401 )
+            {
+                result = -(0.5*x)+(jbtbl1401(x)+0.5*x)*Math.Sqrt((double)1401/(double)n);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                result = Math.Exp(result);
+                return result;
+            }
+            return result;
+        }
+
+
+        private static double jbtbl5(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            if( (double)(s)<=(double)(0.4000) )
+            {
+                x = 2*(s-0.000000)/0.400000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -1.097885e-20, ref tj, ref tj1, ref result);
+                jbcheb(x, -2.854501e-20, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.756616e-20, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            if( (double)(s)<=(double)(1.1000) )
+            {
+                x = 2*(s-0.400000)/0.700000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -1.324545e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.075941e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -9.772272e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, 3.175686e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.576162e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.126861e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, -3.434425e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, -2.790359e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, 2.809178e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, -5.479704e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, 3.717040e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, -5.294170e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, 2.880632e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, -3.023344e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.601531e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, -7.920403e-02, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            result = -(5.188419e+02*(s-1.100000e+00))-4.767297e+00;
+            return result;
+        }
+
+
+        private static double jbtbl6(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            if( (double)(s)<=(double)(0.2500) )
+            {
+                x = 2*(s-0.000000)/0.250000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -2.274707e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, -5.700471e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, -3.425764e-04, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            if( (double)(s)<=(double)(1.3000) )
+            {
+                x = 2*(s-0.250000)/1.050000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -1.339000e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -2.011104e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -8.168177e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.085666e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, 7.738606e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, 7.022876e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, 3.462402e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, 6.908270e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, -8.230772e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.006996e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, -5.410222e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, -2.893768e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, 8.114564e-04, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            if( (double)(s)<=(double)(1.8500) )
+            {
+                x = 2*(s-1.300000)/0.550000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -6.794311e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -3.578700e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.394664e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -7.928290e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, -4.813273e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, -3.076063e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.835380e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.013013e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, -5.058903e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.856915e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, -6.710887e-03, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            result = -(1.770029e+02*(s-1.850000e+00))-1.371015e+01;
+            return result;
+        }
+
+
+        private static double jbtbl7(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            if( (double)(s)<=(double)(1.4000) )
+            {
+                x = 2*(s-0.000000)/1.400000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -1.093681e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.695911e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -7.473192e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.203236e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, 6.590379e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, 6.291876e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, 3.132007e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, 9.411147e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.180067e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, -3.487610e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, -2.436561e-03, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            if( (double)(s)<=(double)(3.0000) )
+            {
+                x = 2*(s-1.400000)/1.600000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -5.947854e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -2.772675e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -4.707912e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.691171e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, -4.132795e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.481310e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, 2.867536e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, 8.772327e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, 5.033387e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.378277e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, -2.497964e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, -3.636814e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, -9.581640e-04, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            if( (double)(s)<=(double)(3.2000) )
+            {
+                x = 2*(s-3.000000)/0.200000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -7.511008e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -8.140472e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.682053e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -2.568561e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.933930e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -8.140472e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, -3.895025e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -8.140472e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.933930e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -2.568561e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.682053e+00, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            result = -(1.824116e+03*(s-3.200000e+00))-1.440330e+01;
+            return result;
+        }
+
+
+        private static double jbtbl8(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            if( (double)(s)<=(double)(1.3000) )
+            {
+                x = 2*(s-0.000000)/1.300000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -7.199015e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.095921e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -4.736828e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.047438e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, -2.484320e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, 7.937923e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, 4.810470e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, 2.139780e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, 6.708443e-04, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            if( (double)(s)<=(double)(2.0000) )
+            {
+                x = 2*(s-1.300000)/0.700000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -3.378966e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -7.802461e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.547593e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, -6.241042e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.203274e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, 5.201990e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, -5.125597e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.584426e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, 2.546069e-04, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            if( (double)(s)<=(double)(5.0000) )
+            {
+                x = 2*(s-2.000000)/3.000000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -6.828366e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -3.137533e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -5.016671e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.745637e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, -5.189801e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.621610e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, -6.741122e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, -4.516368e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, 3.552085e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, 2.787029e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, 5.359774e-03, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            result = -(5.087028e+00*(s-5.000000e+00))-1.071300e+01;
+            return result;
+        }
+
+
+        private static double jbtbl9(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            if( (double)(s)<=(double)(1.3000) )
+            {
+                x = 2*(s-0.000000)/1.300000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -6.279320e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, -9.277151e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, -3.669339e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, -7.086149e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.333816e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, 3.871249e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, 2.007048e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, 7.482245e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, 2.355615e-04, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            if( (double)(s)<=(double)(2.0000) )
+            {
+                x = 2*(s-1.300000)/0.700000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -2.981430e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -7.972248e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.747737e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, -3.808530e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, -7.888305e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, 9.001302e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.378767e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.108510e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, 5.915372e-04, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            if( (double)(s)<=(double)(7.0000) )
+            {
+                x = 2*(s-2.000000)/5.000000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -6.387463e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -2.845231e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.809956e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, -7.543461e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, -4.880397e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.160074e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, -7.356527e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, -4.394428e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, 9.619892e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, -2.758763e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, 4.790977e-05, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            result = -(2.020952e+00*(s-7.000000e+00))-9.516623e+00;
+            return result;
+        }
+
+
+        private static double jbtbl10(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            if( (double)(s)<=(double)(1.2000) )
+            {
+                x = 2*(s-0.000000)/1.200000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -4.590993e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, -6.562730e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, -2.353934e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, -4.069933e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.849151e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, 8.931406e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, 3.636295e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.178340e-05, ref tj, ref tj1, ref result);
+                jbcheb(x, -8.917749e-05, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            if( (double)(s)<=(double)(2.0000) )
+            {
+                x = 2*(s-1.200000)/0.800000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -2.537658e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -9.962401e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.838715e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.055792e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, -2.580316e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.781701e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, 3.770362e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, -4.838983e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, -6.999052e-04, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            if( (double)(s)<=(double)(7.0000) )
+            {
+                x = 2*(s-2.000000)/5.000000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -5.337524e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.877029e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, 4.734650e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, -4.249254e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, 3.320250e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, -6.432266e-03, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            result = -(8.711035e-01*(s-7.000000e+00))-7.212811e+00;
+            return result;
+        }
+
+
+        private static double jbtbl11(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            if( (double)(s)<=(double)(1.2000) )
+            {
+                x = 2*(s-0.000000)/1.200000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -4.339517e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, -6.051558e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, -2.000992e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, -3.022547e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, -9.808401e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, 5.592870e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, 3.575081e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, 2.086173e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, 6.089011e-05, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            if( (double)(s)<=(double)(2.2500) )
+            {
+                x = 2*(s-1.200000)/1.050000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -2.523221e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.068388e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, 2.179661e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.555524e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, -3.238964e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, 7.364320e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, 4.895771e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.762774e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, -8.201340e-04, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            if( (double)(s)<=(double)(8.0000) )
+            {
+                x = 2*(s-2.250000)/5.750000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -5.212179e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.684579e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, 8.299519e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, -3.606261e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, 7.310869e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, -3.320115e-03, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            result = -(5.715445e-01*(s-8.000000e+00))-6.845834e+00;
+            return result;
+        }
+
+
+        private static double jbtbl12(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            if( (double)(s)<=(double)(1.0000) )
+            {
+                x = 2*(s-0.000000)/1.000000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -2.736742e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, -3.657836e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.047209e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.319599e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, -5.545631e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, 9.280445e-05, ref tj, ref tj1, ref result);
+                jbcheb(x, 2.815679e-05, ref tj, ref tj1, ref result);
+                jbcheb(x, -2.213519e-05, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.256838e-05, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            if( (double)(s)<=(double)(3.0000) )
+            {
+                x = 2*(s-1.000000)/2.000000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -2.573947e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.515287e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, 3.611880e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, -3.271311e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, -6.495815e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, 4.141186e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, 7.180886e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.388211e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, 4.890761e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, 3.233175e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, -2.946156e-03, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            if( (double)(s)<=(double)(12.0000) )
+            {
+                x = 2*(s-3.000000)/9.000000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -5.947819e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -2.034157e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, 6.878986e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, -4.078603e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, 6.990977e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, -2.866215e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, 3.897866e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, 2.512252e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, 2.073743e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, 3.022621e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.501343e-03, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            result = -(2.877243e-01*(s-1.200000e+01))-7.936839e+00;
+            return result;
+        }
+
+
+        private static double jbtbl13(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            if( (double)(s)<=(double)(1.0000) )
+            {
+                x = 2*(s-0.000000)/1.000000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -2.713276e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, -3.557541e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, -9.459092e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.044145e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, -2.546132e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.002374e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, 2.349456e-05, ref tj, ref tj1, ref result);
+                jbcheb(x, -7.025669e-05, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.590242e-05, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            if( (double)(s)<=(double)(3.0000) )
+            {
+                x = 2*(s-1.000000)/2.000000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -2.454383e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.467539e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, 3.270774e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, -8.075763e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, -6.611647e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, 2.990785e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, 8.109212e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.135031e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, 5.915919e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, 3.522390e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.144701e-03, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            if( (double)(s)<=(double)(13.0000) )
+            {
+                x = 2*(s-3.000000)/10.000000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -5.736127e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.920809e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.175858e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, -4.002049e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.158966e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, -3.157781e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, 2.762172e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, 5.780347e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.193310e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, -2.442421e-05, ref tj, ref tj1, ref result);
+                jbcheb(x, 2.547756e-03, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            result = -(2.799944e-01*(s-1.300000e+01))-7.566269e+00;
+            return result;
+        }
+
+
+        private static double jbtbl14(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            if( (double)(s)<=(double)(1.0000) )
+            {
+                x = 2*(s-0.000000)/1.000000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -2.698527e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, -3.479081e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, -8.640733e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, -8.466899e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.469485e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, 2.150009e-05, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.965975e-05, ref tj, ref tj1, ref result);
+                jbcheb(x, -4.710210e-05, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.327808e-05, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            if( (double)(s)<=(double)(3.0000) )
+            {
+                x = 2*(s-1.000000)/2.000000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -2.350359e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.421365e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, 2.960468e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.149167e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, -6.361109e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.976022e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.082700e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, -8.563328e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.453123e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, 2.917559e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.151067e-05, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            if( (double)(s)<=(double)(15.0000) )
+            {
+                x = 2*(s-3.000000)/12.000000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -5.746892e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -2.010441e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.566146e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, -5.129690e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.929724e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, -2.524227e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, 3.192933e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, -4.254730e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.620685e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, 7.289618e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, -2.112350e-03, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            result = -(2.590621e-01*(s-1.500000e+01))-7.632238e+00;
+            return result;
+        }
+
+
+        private static double jbtbl15(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            if( (double)(s)<=(double)(2.0000) )
+            {
+                x = 2*(s-0.000000)/2.000000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -1.043660e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.361653e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -3.009497e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, 4.951784e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, 4.377903e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.003253e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.271309e-03, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            if( (double)(s)<=(double)(5.0000) )
+            {
+                x = 2*(s-2.000000)/3.000000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -3.582778e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -8.349578e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, 9.476514e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, -2.717385e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.222591e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, -6.635124e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, 2.815993e-03, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            if( (double)(s)<=(double)(17.0000) )
+            {
+                x = 2*(s-5.000000)/12.000000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -6.115476e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.655936e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, 8.404310e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, -2.663794e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, 8.868618e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.381447e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, 9.444801e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.581503e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, -9.468696e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.728509e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.206470e-03, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            result = -(1.927937e-01*(s-1.700000e+01))-7.700983e+00;
+            return result;
+        }
+
+
+        private static double jbtbl16(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            if( (double)(s)<=(double)(2.0000) )
+            {
+                x = 2*(s-0.000000)/2.000000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -1.002570e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.298141e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -2.832803e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, 3.877026e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, 3.539436e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, 8.439658e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, -4.756911e-04, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            if( (double)(s)<=(double)(5.0000) )
+            {
+                x = 2*(s-2.000000)/3.000000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -3.486198e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -8.242944e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.020002e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, -3.130531e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.512373e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, -8.054876e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, 3.556839e-03, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            if( (double)(s)<=(double)(20.0000) )
+            {
+                x = 2*(s-5.000000)/15.000000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -6.241608e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.832655e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.340545e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, -3.361143e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.283219e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, 3.484549e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.805968e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, -2.057243e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.454439e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, -2.177513e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.819209e-03, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            result = -(2.391580e-01*(s-2.000000e+01))-7.963205e+00;
+            return result;
+        }
+
+
+        private static double jbtbl17(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            if( (double)(s)<=(double)(3.0000) )
+            {
+                x = 2*(s-0.000000)/3.000000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -1.566973e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.810330e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -4.840039e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, 2.337294e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, -5.383549e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, -5.556515e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, -8.656965e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.404569e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, 6.447867e-03, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            if( (double)(s)<=(double)(6.0000) )
+            {
+                x = 2*(s-3.000000)/3.000000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -3.905684e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -6.222920e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, 4.146667e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, -4.809176e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.057028e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.211838e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, -4.099683e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.161105e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, 2.225465e-04, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            if( (double)(s)<=(double)(24.0000) )
+            {
+                x = 2*(s-6.000000)/18.000000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -6.594282e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.917838e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.455980e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, -2.999589e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, 5.604263e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, -3.484445e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.819937e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, -2.930390e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, 2.771761e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, -6.232581e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, -7.029083e-04, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            result = -(2.127771e-01*(s-2.400000e+01))-8.400197e+00;
+            return result;
+        }
+
+
+        private static double jbtbl18(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            if( (double)(s)<=(double)(3.0000) )
+            {
+                x = 2*(s-0.000000)/3.000000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -1.526802e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.762373e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -5.598890e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, 2.189437e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, 5.971721e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, -4.823067e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.064501e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.014932e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, 5.953513e-03, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            if( (double)(s)<=(double)(6.0000) )
+            {
+                x = 2*(s-3.000000)/3.000000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -3.818669e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -6.070918e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, 4.277196e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, -4.879817e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, 6.887357e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.638451e-05, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.502800e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, -3.165796e-05, ref tj, ref tj1, ref result);
+                jbcheb(x, 5.034960e-05, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            if( (double)(s)<=(double)(20.0000) )
+            {
+                x = 2*(s-6.000000)/14.000000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -6.010656e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.496296e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.002227e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, -2.338250e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, 4.137036e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, -2.586202e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, -9.736384e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.332251e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.877982e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.160963e-05, ref tj, ref tj1, ref result);
+                jbcheb(x, -2.547247e-03, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            result = -(1.684623e-01*(s-2.000000e+01))-7.428883e+00;
+            return result;
+        }
+
+
+        private static double jbtbl19(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            if( (double)(s)<=(double)(3.0000) )
+            {
+                x = 2*(s-0.000000)/3.000000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -1.490213e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.719633e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -6.459123e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, 2.034878e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.113868e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, -4.030922e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.054022e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, 7.525623e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, 5.277360e-03, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            if( (double)(s)<=(double)(6.0000) )
+            {
+                x = 2*(s-3.000000)/3.000000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -3.744750e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -5.977749e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, 4.223716e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, -5.363889e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, 5.711774e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, -5.557257e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, 4.254794e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, 9.034207e-05, ref tj, ref tj1, ref result);
+                jbcheb(x, 5.498107e-05, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            if( (double)(s)<=(double)(20.0000) )
+            {
+                x = 2*(s-6.000000)/14.000000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -5.872768e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.430689e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.136575e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.726627e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, 3.421110e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.581510e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, -5.559520e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, -6.838208e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, 8.428839e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, -7.170682e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, -6.006647e-04, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            result = -(1.539373e-01*(s-2.000000e+01))-7.206941e+00;
+            return result;
+        }
+
+
+        private static double jbtbl20(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            if( (double)(s)<=(double)(4.0000) )
+            {
+                x = 2*(s-0.000000)/4.000000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -1.854794e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.948947e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.632184e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, 2.139397e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.006237e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, -3.810031e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, 3.573620e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, 9.951242e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.274092e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, -3.464196e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, 4.882139e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.575144e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.822804e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, -7.061348e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, 5.908404e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.978353e-04, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            if( (double)(s)<=(double)(15.0000) )
+            {
+                x = 2*(s-4.000000)/11.000000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -5.030989e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.327151e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.346404e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, -2.840051e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, 7.578551e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, -9.813886e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, 5.905973e-05, ref tj, ref tj1, ref result);
+                jbcheb(x, -5.358489e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, -3.450795e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, -6.941157e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, -7.432418e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, -2.070537e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, 9.375654e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, 5.367378e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, 9.890859e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, 6.679782e-04, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            if( (double)(s)<=(double)(25.0000) )
+            {
+                x = 2*(s-15.000000)/10.000000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -7.015854e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -7.487737e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, 2.244254e-02, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            result = -(1.318007e-01*(s-2.500000e+01))-7.742185e+00;
+            return result;
+        }
+
+
+        private static double jbtbl30(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            if( (double)(s)<=(double)(4.0000) )
+            {
+                x = 2*(s-0.000000)/4.000000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -1.630822e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.724298e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, 7.872756e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.658268e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, -3.573597e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, -2.994157e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, 5.994825e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, 7.394303e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, -5.785029e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.990264e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.037838e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, 6.755546e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.774473e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, -2.821395e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.392603e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.353313e-04, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            if( (double)(s)<=(double)(15.0000) )
+            {
+                x = 2*(s-4.000000)/11.000000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -4.539322e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.197018e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.396848e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, -2.804293e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, 6.867928e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, -2.768758e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, 5.211792e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, 4.925799e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, 5.046235e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, -9.536469e-05, ref tj, ref tj1, ref result);
+                jbcheb(x, -6.489642e-04, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            if( (double)(s)<=(double)(25.0000) )
+            {
+                x = 2*(s-15.000000)/10.000000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -6.263462e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -6.177316e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, 2.590637e-02, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            result = -(1.028212e-01*(s-2.500000e+01))-6.855288e+00;
+            return result;
+        }
+
+
+        private static double jbtbl50(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            if( (double)(s)<=(double)(4.0000) )
+            {
+                x = 2*(s-0.000000)/4.000000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -1.436279e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.519711e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.148699e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.001204e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, -3.207620e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.034778e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.220322e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.033260e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, 2.588280e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.851653e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.287733e-04, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            if( (double)(s)<=(double)(15.0000) )
+            {
+                x = 2*(s-4.000000)/11.000000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -4.234645e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.189127e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.429738e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, -3.058822e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, 9.086776e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.445783e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.311671e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, -7.261298e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, 6.496987e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, 2.605249e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, 8.162282e-04, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            if( (double)(s)<=(double)(25.0000) )
+            {
+                x = 2*(s-15.000000)/10.000000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -5.921095e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -5.888603e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, 3.080113e-02, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            result = -(9.313116e-02*(s-2.500000e+01))-6.479154e+00;
+            return result;
+        }
+
+
+        private static double jbtbl65(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            if( (double)(s)<=(double)(4.0000) )
+            {
+                x = 2*(s-0.000000)/4.000000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -1.360024e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.434631e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -6.514580e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, 7.332038e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.158197e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, -5.121233e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.051056e-03, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            if( (double)(s)<=(double)(15.0000) )
+            {
+                x = 2*(s-4.000000)/11.000000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -4.148601e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.214233e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.487977e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, -3.424720e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.116715e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, -4.043152e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.718149e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.313701e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, 3.097305e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, 2.181031e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.256975e-04, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            if( (double)(s)<=(double)(25.0000) )
+            {
+                x = 2*(s-15.000000)/10.000000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -5.858951e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -5.895179e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, 2.933237e-02, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            result = -(9.443768e-02*(s-2.500000e+01))-6.419137e+00;
+            return result;
+        }
+
+
+        private static double jbtbl100(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            if( (double)(s)<=(double)(4.0000) )
+            {
+                x = 2*(s-0.000000)/4.000000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -1.257021e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.313418e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.628931e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, 4.264287e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.518487e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.499826e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, -4.836044e-04, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            if( (double)(s)<=(double)(15.0000) )
+            {
+                x = 2*(s-4.000000)/11.000000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -4.056508e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.279690e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.665746e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, -4.290012e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.487632e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, -5.704465e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, 2.211669e-03, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            if( (double)(s)<=(double)(25.0000) )
+            {
+                x = 2*(s-15.000000)/10.000000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -5.866099e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -6.399767e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, 2.498208e-02, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            result = -(1.080097e-01*(s-2.500000e+01))-6.481094e+00;
+            return result;
+        }
+
+
+        private static double jbtbl130(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            if( (double)(s)<=(double)(4.0000) )
+            {
+                x = 2*(s-0.000000)/4.000000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -1.207999e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.253864e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.618032e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, 3.112729e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.210546e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, -4.732602e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, -2.410527e-04, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            if( (double)(s)<=(double)(15.0000) )
+            {
+                x = 2*(s-4.000000)/11.000000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -4.026324e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.331990e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.779129e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, -4.674749e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.669077e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, -5.679136e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, 8.833221e-04, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            if( (double)(s)<=(double)(25.0000) )
+            {
+                x = 2*(s-15.000000)/10.000000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -5.893951e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -6.475304e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, 3.116734e-02, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            result = -(1.045722e-01*(s-2.500000e+01))-6.510314e+00;
+            return result;
+        }
+
+
+        private static double jbtbl200(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            if( (double)(s)<=(double)(4.0000) )
+            {
+                x = 2*(s-0.000000)/4.000000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -1.146155e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.177398e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.297970e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.869745e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.717288e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.982108e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, 6.427636e-05, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            if( (double)(s)<=(double)(15.0000) )
+            {
+                x = 2*(s-4.000000)/11.000000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -4.034235e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.455006e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.942996e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, -4.973795e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.418812e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, -3.156778e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, 4.896705e-05, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            if( (double)(s)<=(double)(25.0000) )
+            {
+                x = 2*(s-15.000000)/10.000000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -6.086071e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -7.152176e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, 3.725393e-02, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            result = -(1.132404e-01*(s-2.500000e+01))-6.764034e+00;
+            return result;
+        }
+
+
+        private static double jbtbl301(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            if( (double)(s)<=(double)(4.0000) )
+            {
+                x = 2*(s-0.000000)/4.000000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -1.104290e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.125800e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -9.595847e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.219666e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.502210e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, -6.414543e-05, ref tj, ref tj1, ref result);
+                jbcheb(x, 6.754115e-05, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            if( (double)(s)<=(double)(15.0000) )
+            {
+                x = 2*(s-4.000000)/11.000000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -4.065955e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.582060e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, 2.004472e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, -4.709092e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.105779e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.197391e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, -8.386780e-04, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            if( (double)(s)<=(double)(25.0000) )
+            {
+                x = 2*(s-15.000000)/10.000000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -6.311384e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -7.918763e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, 3.626584e-02, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            result = -(1.293626e-01*(s-2.500000e+01))-7.066995e+00;
+            return result;
+        }
+
+
+        private static double jbtbl501(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            if( (double)(s)<=(double)(4.0000) )
+            {
+                x = 2*(s-0.000000)/4.000000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -1.067426e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.079765e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -5.463005e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, 6.875659e-03, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            if( (double)(s)<=(double)(15.0000) )
+            {
+                x = 2*(s-4.000000)/11.000000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -4.127574e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.740694e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, 2.044502e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, -3.746714e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, 3.810594e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.197111e-03, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            if( (double)(s)<=(double)(25.0000) )
+            {
+                x = 2*(s-15.000000)/10.000000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -6.628194e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -8.846221e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, 4.386405e-02, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            result = -(1.418332e-01*(s-2.500000e+01))-7.468952e+00;
+            return result;
+        }
+
+
+        private static double jbtbl701(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            if( (double)(s)<=(double)(4.0000) )
+            {
+                x = 2*(s-0.000000)/4.000000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -1.050999e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.059769e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -3.922680e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, 4.847054e-03, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            if( (double)(s)<=(double)(15.0000) )
+            {
+                x = 2*(s-4.000000)/11.000000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -4.192182e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.860007e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.963942e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, -2.838711e-02, ref tj, ref tj1, ref result);
+                jbcheb(x, -2.893112e-04, ref tj, ref tj1, ref result);
+                jbcheb(x, 2.159788e-03, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            if( (double)(s)<=(double)(25.0000) )
+            {
+                x = 2*(s-15.000000)/10.000000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -6.917851e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -9.817020e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, 5.383727e-02, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            result = -(1.532706e-01*(s-2.500000e+01))-7.845715e+00;
+            return result;
+        }
+
+
+        private static double jbtbl1401(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            if( (double)(s)<=(double)(4.0000) )
+            {
+                x = 2*(s-0.000000)/4.000000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -1.026266e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.030061e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.259222e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, 2.536254e-03, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            if( (double)(s)<=(double)(15.0000) )
+            {
+                x = 2*(s-4.000000)/11.000000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -4.329849e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -2.095443e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, 1.759363e-01, ref tj, ref tj1, ref result);
+                jbcheb(x, -7.751359e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, -6.124368e-03, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.793114e-03, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            if( (double)(s)<=(double)(25.0000) )
+            {
+                x = 2*(s-15.000000)/10.000000-1;
+                tj = 1;
+                tj1 = x;
+                jbcheb(x, -7.544330e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, -1.225382e+00, ref tj, ref tj1, ref result);
+                jbcheb(x, 5.392349e-02, ref tj, ref tj1, ref result);
+                if( (double)(result)>(double)(0) )
+                {
+                    result = 0;
+                }
+                return result;
+            }
+            result = -(2.019375e-01*(s-2.500000e+01))-8.715788e+00;
+            return result;
+        }
+
+
+        private static void jbcheb(double x,
+            double c,
+            ref double tj,
+            ref double tj1,
+            ref double r)
+        {
+            double t = 0;
+
+            r = r+c*tj;
+            t = 2*x*tj1-tj;
+            tj = tj1;
+            tj1 = t;
+        }
+
+
+    }
+    public class mannwhitneyu
+    {
+        /*************************************************************************
+        Mann-Whitney U-test
+
+        This test checks hypotheses about whether X  and  Y  are  samples  of  two
+        continuous distributions of the same shape  and  same  median  or  whether
+        their medians are different.
+
+        The following tests are performed:
+            * two-tailed test (null hypothesis - the medians are equal)
+            * left-tailed test (null hypothesis - the median of the  first  sample
+              is greater than or equal to the median of the second sample)
+            * right-tailed test (null hypothesis - the median of the first  sample
+              is less than or equal to the median of the second sample).
+
+        Requirements:
+            * the samples are independent
+            * X and Y are continuous distributions (or discrete distributions well-
+              approximating continuous distributions)
+            * distributions of X and Y have the  same  shape.  The  only  possible
+              difference is their position (i.e. the value of the median)
+            * the number of elements in each sample is not less than 5
+            * the scale of measurement should be ordinal, interval or ratio  (i.e.
+              the test could not be applied to nominal variables).
+
+        The test is non-parametric and doesn't require distributions to be normal.
+
+        Input parameters:
+            X   -   sample 1. Array whose index goes from 0 to N-1.
+            N   -   size of the sample. N>=5
+            Y   -   sample 2. Array whose index goes from 0 to M-1.
+            M   -   size of the sample. M>=5
+
+        Output parameters:
+            BothTails   -   p-value for two-tailed test.
+                            If BothTails is less than the given significance level
+                            the null hypothesis is rejected.
+            LeftTail    -   p-value for left-tailed test.
+                            If LeftTail is less than the given significance level,
+                            the null hypothesis is rejected.
+            RightTail   -   p-value for right-tailed test.
+                            If RightTail is less than the given significance level
+                            the null hypothesis is rejected.
+
+        To calculate p-values, special approximation is used. This method lets  us
+        calculate p-values with satisfactory  accuracy  in  interval  [0.0001, 1].
+        There is no approximation outside the [0.0001, 1] interval. Therefore,  if
+        the significance level outlies this interval, the test returns 0.0001.
+
+        Relative precision of approximation of p-value:
+
+        N          M          Max.err.   Rms.err.
+        5..10      N..10      1.4e-02    6.0e-04
+        5..10      N..100     2.2e-02    5.3e-06
+        10..15     N..15      1.0e-02    3.2e-04
+        10..15     N..100     1.0e-02    2.2e-05
+        15..100    N..100     6.1e-03    2.7e-06
+
+        For N,M>100 accuracy checks weren't put into  practice,  but  taking  into
+        account characteristics of asymptotic approximation used, precision should
+        not be sharply different from the values for interval [5, 100].
+
+          -- ALGLIB --
+             Copyright 09.04.2007 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mannwhitneyutest(double[] x,
+            int n,
+            double[] y,
+            int m,
+            ref double bothtails,
+            ref double lefttail,
+            ref double righttail)
+        {
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int t = 0;
+            double tmp = 0;
+            int tmpi = 0;
+            int ns = 0;
+            double[] r = new double[0];
+            int[] c = new int[0];
+            double u = 0;
+            double p = 0;
+            double mp = 0;
+            double s = 0;
+            double sigma = 0;
+            double mu = 0;
+            int tiecount = 0;
+            int[] tiesize = new int[0];
+
+            bothtails = 0;
+            lefttail = 0;
+            righttail = 0;
+
+            
+            //
+            // Prepare
+            //
+            if( n<=4 || m<=4 )
+            {
+                bothtails = 1.0;
+                lefttail = 1.0;
+                righttail = 1.0;
+                return;
+            }
+            ns = n+m;
+            r = new double[ns-1+1];
+            c = new int[ns-1+1];
+            for(i=0; i<=n-1; i++)
+            {
+                r[i] = x[i];
+                c[i] = 0;
+            }
+            for(i=0; i<=m-1; i++)
+            {
+                r[n+i] = y[i];
+                c[n+i] = 1;
+            }
+            
+            //
+            // sort {R, C}
+            //
+            if( ns!=1 )
+            {
+                i = 2;
+                do
+                {
+                    t = i;
+                    while( t!=1 )
+                    {
+                        k = t/2;
+                        if( (double)(r[k-1])>=(double)(r[t-1]) )
+                        {
+                            t = 1;
+                        }
+                        else
+                        {
+                            tmp = r[k-1];
+                            r[k-1] = r[t-1];
+                            r[t-1] = tmp;
+                            tmpi = c[k-1];
+                            c[k-1] = c[t-1];
+                            c[t-1] = tmpi;
+                            t = k;
+                        }
+                    }
+                    i = i+1;
+                }
+                while( i<=ns );
+                i = ns-1;
+                do
+                {
+                    tmp = r[i];
+                    r[i] = r[0];
+                    r[0] = tmp;
+                    tmpi = c[i];
+                    c[i] = c[0];
+                    c[0] = tmpi;
+                    t = 1;
+                    while( t!=0 )
+                    {
+                        k = 2*t;
+                        if( k>i )
+                        {
+                            t = 0;
+                        }
+                        else
+                        {
+                            if( k<i )
+                            {
+                                if( (double)(r[k])>(double)(r[k-1]) )
+                                {
+                                    k = k+1;
+                                }
+                            }
+                            if( (double)(r[t-1])>=(double)(r[k-1]) )
+                            {
+                                t = 0;
+                            }
+                            else
+                            {
+                                tmp = r[k-1];
+                                r[k-1] = r[t-1];
+                                r[t-1] = tmp;
+                                tmpi = c[k-1];
+                                c[k-1] = c[t-1];
+                                c[t-1] = tmpi;
+                                t = k;
+                            }
+                        }
+                    }
+                    i = i-1;
+                }
+                while( i>=1 );
+            }
+            
+            //
+            // compute tied ranks
+            //
+            i = 0;
+            tiecount = 0;
+            tiesize = new int[ns-1+1];
+            while( i<=ns-1 )
+            {
+                j = i+1;
+                while( j<=ns-1 )
+                {
+                    if( (double)(r[j])!=(double)(r[i]) )
+                    {
+                        break;
+                    }
+                    j = j+1;
+                }
+                for(k=i; k<=j-1; k++)
+                {
+                    r[k] = 1+(double)(i+j-1)/(double)2;
+                }
+                tiesize[tiecount] = j-i;
+                tiecount = tiecount+1;
+                i = j;
+            }
+            
+            //
+            // Compute U
+            //
+            u = 0;
+            for(i=0; i<=ns-1; i++)
+            {
+                if( c[i]==0 )
+                {
+                    u = u+r[i];
+                }
+            }
+            u = n*m+n*(n+1)/2-u;
+            
+            //
+            // Result
+            //
+            mu = (double)(n*m)/(double)2;
+            tmp = ns*(math.sqr(ns)-1)/12;
+            for(i=0; i<=tiecount-1; i++)
+            {
+                tmp = tmp-tiesize[i]*(math.sqr(tiesize[i])-1)/12;
+            }
+            sigma = Math.Sqrt((double)(m*n)/(double)ns/(ns-1)*tmp);
+            s = (u-mu)/sigma;
+            if( (double)(s)<=(double)(0) )
+            {
+                p = Math.Exp(usigma(-((u-mu)/sigma), n, m));
+                mp = 1-Math.Exp(usigma(-((u-1-mu)/sigma), n, m));
+            }
+            else
+            {
+                mp = Math.Exp(usigma((u-mu)/sigma, n, m));
+                p = 1-Math.Exp(usigma((u+1-mu)/sigma, n, m));
+            }
+            bothtails = Math.Max(2*Math.Min(p, mp), 1.0E-4);
+            lefttail = Math.Max(mp, 1.0E-4);
+            righttail = Math.Max(p, 1.0E-4);
+        }
+
+
+        /*************************************************************************
+        Sequential Chebyshev interpolation.
+        *************************************************************************/
+        private static void ucheb(double x,
+            double c,
+            ref double tj,
+            ref double tj1,
+            ref double r)
+        {
+            double t = 0;
+
+            r = r+c*tj;
+            t = 2*x*tj1-tj;
+            tj = tj1;
+            tj1 = t;
+        }
+
+
+        /*************************************************************************
+        Three-point polynomial interpolation.
+        *************************************************************************/
+        private static double uninterpolate(double p1,
+            double p2,
+            double p3,
+            int n)
+        {
+            double result = 0;
+            double t1 = 0;
+            double t2 = 0;
+            double t3 = 0;
+            double t = 0;
+            double p12 = 0;
+            double p23 = 0;
+
+            t1 = 1.0/15.0;
+            t2 = 1.0/30.0;
+            t3 = 1.0/100.0;
+            t = 1.0/n;
+            p12 = ((t-t2)*p1+(t1-t)*p2)/(t1-t2);
+            p23 = ((t-t3)*p2+(t2-t)*p3)/(t2-t3);
+            result = ((t-t3)*p12+(t1-t)*p23)/(t1-t3);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(0, N1, N2)
+        *************************************************************************/
+        private static double usigma000(int n1,
+            int n2)
+        {
+            double result = 0;
+            double p1 = 0;
+            double p2 = 0;
+            double p3 = 0;
+
+            p1 = uninterpolate(-6.76984e-01, -6.83700e-01, -6.89873e-01, n2);
+            p2 = uninterpolate(-6.83700e-01, -6.87311e-01, -6.90957e-01, n2);
+            p3 = uninterpolate(-6.89873e-01, -6.90957e-01, -6.92175e-01, n2);
+            result = uninterpolate(p1, p2, p3, n1);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(0.75, N1, N2)
+        *************************************************************************/
+        private static double usigma075(int n1,
+            int n2)
+        {
+            double result = 0;
+            double p1 = 0;
+            double p2 = 0;
+            double p3 = 0;
+
+            p1 = uninterpolate(-1.44500e+00, -1.45906e+00, -1.47063e+00, n2);
+            p2 = uninterpolate(-1.45906e+00, -1.46856e+00, -1.47644e+00, n2);
+            p3 = uninterpolate(-1.47063e+00, -1.47644e+00, -1.48100e+00, n2);
+            result = uninterpolate(p1, p2, p3, n1);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(1.5, N1, N2)
+        *************************************************************************/
+        private static double usigma150(int n1,
+            int n2)
+        {
+            double result = 0;
+            double p1 = 0;
+            double p2 = 0;
+            double p3 = 0;
+
+            p1 = uninterpolate(-2.65380e+00, -2.67352e+00, -2.69011e+00, n2);
+            p2 = uninterpolate(-2.67352e+00, -2.68591e+00, -2.69659e+00, n2);
+            p3 = uninterpolate(-2.69011e+00, -2.69659e+00, -2.70192e+00, n2);
+            result = uninterpolate(p1, p2, p3, n1);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(2.25, N1, N2)
+        *************************************************************************/
+        private static double usigma225(int n1,
+            int n2)
+        {
+            double result = 0;
+            double p1 = 0;
+            double p2 = 0;
+            double p3 = 0;
+
+            p1 = uninterpolate(-4.41465e+00, -4.42260e+00, -4.43702e+00, n2);
+            p2 = uninterpolate(-4.42260e+00, -4.41639e+00, -4.41928e+00, n2);
+            p3 = uninterpolate(-4.43702e+00, -4.41928e+00, -4.41030e+00, n2);
+            result = uninterpolate(p1, p2, p3, n1);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(3.0, N1, N2)
+        *************************************************************************/
+        private static double usigma300(int n1,
+            int n2)
+        {
+            double result = 0;
+            double p1 = 0;
+            double p2 = 0;
+            double p3 = 0;
+
+            p1 = uninterpolate(-6.89839e+00, -6.83477e+00, -6.82340e+00, n2);
+            p2 = uninterpolate(-6.83477e+00, -6.74559e+00, -6.71117e+00, n2);
+            p3 = uninterpolate(-6.82340e+00, -6.71117e+00, -6.64929e+00, n2);
+            result = uninterpolate(p1, p2, p3, n1);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(3.33, N1, N2)
+        *************************************************************************/
+        private static double usigma333(int n1,
+            int n2)
+        {
+            double result = 0;
+            double p1 = 0;
+            double p2 = 0;
+            double p3 = 0;
+
+            p1 = uninterpolate(-8.31272e+00, -8.17096e+00, -8.13125e+00, n2);
+            p2 = uninterpolate(-8.17096e+00, -8.00156e+00, -7.93245e+00, n2);
+            p3 = uninterpolate(-8.13125e+00, -7.93245e+00, -7.82502e+00, n2);
+            result = uninterpolate(p1, p2, p3, n1);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(3.66, N1, N2)
+        *************************************************************************/
+        private static double usigma367(int n1,
+            int n2)
+        {
+            double result = 0;
+            double p1 = 0;
+            double p2 = 0;
+            double p3 = 0;
+
+            p1 = uninterpolate(-9.98837e+00, -9.70844e+00, -9.62087e+00, n2);
+            p2 = uninterpolate(-9.70844e+00, -9.41156e+00, -9.28998e+00, n2);
+            p3 = uninterpolate(-9.62087e+00, -9.28998e+00, -9.11686e+00, n2);
+            result = uninterpolate(p1, p2, p3, n1);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(4.0, N1, N2)
+        *************************************************************************/
+        private static double usigma400(int n1,
+            int n2)
+        {
+            double result = 0;
+            double p1 = 0;
+            double p2 = 0;
+            double p3 = 0;
+
+            p1 = uninterpolate(-1.20250e+01, -1.14911e+01, -1.13231e+01, n2);
+            p2 = uninterpolate(-1.14911e+01, -1.09927e+01, -1.07937e+01, n2);
+            p3 = uninterpolate(-1.13231e+01, -1.07937e+01, -1.05285e+01, n2);
+            result = uninterpolate(p1, p2, p3, n1);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 5, 5)
+        *************************************************************************/
+        private static double utbln5n5(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/2.611165e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -2.596264e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -2.412086e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.858542e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -5.614282e-02, ref tj, ref tj1, ref result);
+            ucheb(x, 3.372686e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 8.524731e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 4.435331e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 1.284665e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 4.184141e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 5.298360e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 7.447272e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -3.938769e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -4.276205e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.138481e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 8.684625e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 1.558104e-03, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 5, 6)
+        *************************************************************************/
+        private static double utbln5n6(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/2.738613e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -2.810459e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -2.684429e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -5.712858e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -8.009324e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -6.644391e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 6.034173e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 4.953498e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 3.279293e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 3.563485e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 4.971952e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 3.506309e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.541406e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -3.283205e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -3.016347e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.221626e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.286752e-03, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 5, 7)
+        *************************************************************************/
+        private static double utbln5n7(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/2.841993e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -2.994677e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -2.923264e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -6.506190e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -1.054280e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -1.794587e-02, ref tj, ref tj1, ref result);
+            ucheb(x, 1.726290e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 4.534180e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 4.517845e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 3.904428e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 3.882443e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 3.482988e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.114875e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.515082e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -1.996056e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -2.293581e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -2.349444e-03, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 5, 8)
+        *************************************************************************/
+        private static double utbln5n8(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/2.927700e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -3.155727e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -3.135078e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -7.247203e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -1.309697e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -2.993725e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -3.567219e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 3.383704e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 5.002188e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 4.487322e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 3.443899e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.688270e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.600339e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 1.874948e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 1.811593e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -1.072353e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -2.659457e-03, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 5, 9)
+        *************************************************************************/
+        private static double utbln5n9(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.000000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -3.298162e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -3.325016e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -7.939852e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -1.563029e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -4.222652e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -9.195200e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 1.445665e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 5.204792e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 4.775217e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 3.527781e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.221948e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.242968e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.607959e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 1.771285e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 6.694026e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -1.481190e-03, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 5, 10)
+        *************************************************************************/
+        private static double utbln5n10(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.061862e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -3.425360e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -3.496710e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -8.587658e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -1.812005e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -5.427637e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -1.515702e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -5.406867e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 4.796295e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 5.237591e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 3.654249e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.181165e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.011665e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.417927e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.534880e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 1.791255e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 1.871512e-05, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 5, 11)
+        *************************************************************************/
+        private static double utbln5n11(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.115427e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -3.539959e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -3.652998e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -9.196503e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -2.054363e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -6.618848e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -2.109411e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -2.786668e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 4.215648e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 5.484220e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 3.935991e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.396191e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 1.894177e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.206979e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.519055e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.210326e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 1.189679e-03, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 5, 12)
+        *************************************************************************/
+        private static double utbln5n12(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.162278e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -3.644007e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -3.796173e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -9.771177e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -2.290043e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -7.794686e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -2.702110e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -5.185959e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 3.416259e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 5.592056e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 4.201530e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.754365e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 1.978945e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.012032e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.304579e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.100378e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 1.728269e-03, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 5, 13)
+        *************************************************************************/
+        private static double utbln5n13(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.203616e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -3.739120e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -3.928117e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.031605e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -2.519403e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -8.962648e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -3.292183e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -7.809293e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.465156e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 5.456278e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 4.446055e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 3.109490e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.218256e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 1.941479e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.058603e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 1.824402e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 1.830947e-03, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 5, 14)
+        *************************************************************************/
+        private static double utbln5n14(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.240370e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -3.826559e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.050370e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.083408e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -2.743164e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -1.012030e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -3.884686e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -1.059656e-02, ref tj, ref tj1, ref result);
+            ucheb(x, 1.327521e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 5.134026e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 4.584201e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 3.440618e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.524133e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 1.990007e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 1.887334e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 1.534977e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 1.705395e-03, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 5, 15)
+        *************************************************************************/
+        private static double utbln5n15(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.250000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -3.851572e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.082033e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.095983e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -2.814595e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -1.073148e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -4.420213e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -1.517175e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -2.344180e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.371393e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.711443e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.228569e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 1.683483e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 1.267112e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 1.156044e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 9.131316e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 1.301023e-03, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 5, 16)
+        *************************************************************************/
+        private static double utbln5n16(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.250000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -3.852210e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.077482e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.091186e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -2.797282e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -1.084994e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -4.667054e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -1.843909e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -5.456732e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -5.039830e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 4.723508e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 3.940608e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 1.478285e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -1.649144e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -4.237703e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -4.707410e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -1.874293e-04, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 5, 17)
+        *************************************************************************/
+        private static double utbln5n17(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.250000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -3.851752e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.071259e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.084700e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -2.758898e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -1.073846e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -4.684838e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -1.964936e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -6.782442e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.956362e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -5.984727e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -5.196936e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -5.558262e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -8.690746e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -1.364855e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.401006e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.546748e-03, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 5, 18)
+        *************************************************************************/
+        private static double utbln5n18(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.250000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -3.850840e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.064799e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.077651e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -2.712659e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -1.049217e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -4.571333e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -1.929809e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -6.752044e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.949464e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -3.896101e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -4.614460e-05, ref tj, ref tj1, ref result);
+            ucheb(x, 1.384357e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -6.489113e-05, ref tj, ref tj1, ref result);
+            ucheb(x, -6.445725e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -8.945636e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -1.424653e-03, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 5, 19)
+        *************************************************************************/
+        private static double utbln5n19(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.250000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -3.850027e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.059159e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.071106e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -2.669960e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -1.022780e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -4.442555e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -1.851335e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -6.433865e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.514465e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 1.332989e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 8.606099e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 1.341945e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 1.402164e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 1.039761e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 5.512831e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -3.284427e-05, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 5, 20)
+        *************************************************************************/
+        private static double utbln5n20(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.250000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -3.849651e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.054729e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.065747e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -2.636243e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -1.003234e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -4.372789e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -1.831551e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -6.763090e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.830626e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -2.122384e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 8.108328e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 1.557983e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 1.945666e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 1.965696e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 1.493236e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 1.162591e-03, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 5, 21)
+        *************************************************************************/
+        private static double utbln5n21(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.250000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -3.849649e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.051155e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.061430e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -2.608869e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -9.902788e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -4.346562e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -1.874709e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -7.682887e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -3.026206e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.534551e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -4.990575e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 3.713334e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 9.737011e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 1.304571e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 1.133110e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 1.123457e-03, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 5, 22)
+        *************************************************************************/
+        private static double utbln5n22(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.250000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -3.849598e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.047605e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.057264e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -2.579513e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -9.749602e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -4.275137e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -1.881768e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -8.177374e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -3.981056e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -2.696290e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.886803e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.085378e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -4.675242e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -5.426367e-05, ref tj, ref tj1, ref result);
+            ucheb(x, 1.039613e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 2.662378e-04, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 5, 23)
+        *************************************************************************/
+        private static double utbln5n23(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.250000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -3.849269e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.043761e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.052735e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -2.544683e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -9.517503e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -4.112082e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -1.782070e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -7.549483e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -3.747329e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -2.694263e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -2.147141e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.526209e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.039173e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -7.235615e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -4.656546e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -3.014423e-04, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 5, 24)
+        *************************************************************************/
+        private static double utbln5n24(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.250000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -3.848925e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.040178e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.048355e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -2.510198e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -9.261134e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -3.915864e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -1.627423e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -6.307345e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -2.732992e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.869652e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.494176e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.047533e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -7.178439e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -5.424171e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -3.829195e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -2.840810e-04, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 5, 25)
+        *************************************************************************/
+        private static double utbln5n25(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.250000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -3.848937e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.037512e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.044866e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -2.483269e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -9.063682e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -3.767778e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -1.508540e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -5.332756e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.881511e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.124041e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -8.368456e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -4.930499e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -2.779630e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -2.029528e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -1.658678e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -1.289695e-04, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 5, 26)
+        *************************************************************************/
+        private static double utbln5n26(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.250000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -3.849416e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.035915e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.042493e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -2.466021e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -8.956432e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -3.698914e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -1.465689e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -5.035254e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.674614e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -9.492734e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -7.014021e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -3.944953e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -2.255750e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -2.075841e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -1.989330e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -2.134862e-04, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 5, 27)
+        *************************************************************************/
+        private static double utbln5n27(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.250000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -3.850070e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.034815e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.040650e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -2.453117e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -8.886426e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -3.661702e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -1.452346e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -5.002476e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.720126e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.001400e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -7.729826e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -4.740640e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -3.206333e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -3.366093e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -3.193471e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -3.804091e-04, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 5, 28)
+        *************************************************************************/
+        private static double utbln5n28(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.250000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -3.850668e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.033786e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.038853e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -2.440281e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -8.806020e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -3.612883e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -1.420436e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -4.787982e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.535230e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -8.263121e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -5.849609e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -2.863967e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -1.391610e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -1.720294e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -1.952273e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -2.901413e-04, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 5, 29)
+        *************************************************************************/
+        private static double utbln5n29(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.250000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -3.851217e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.032834e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.037113e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -2.427762e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -8.719146e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -3.557172e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -1.375498e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -4.452033e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.187516e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -4.916936e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -2.065533e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 1.067301e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 2.615824e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 2.432244e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 1.417795e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 4.710038e-05, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 5, 30)
+        *************************************************************************/
+        private static double utbln5n30(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.250000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -3.851845e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.032148e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.035679e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -2.417758e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -8.655330e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -3.522132e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -1.352106e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -4.326911e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.064969e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -3.813321e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -5.683881e-05, ref tj, ref tj1, ref result);
+            ucheb(x, 2.813346e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 4.627085e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 4.832107e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 3.519336e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 2.888530e-04, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 5, 100)
+        *************************************************************************/
+        private static double utbln5n100(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.250000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -3.877940e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.039324e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.022243e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -2.305825e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -7.960119e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -3.112000e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -1.138868e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -3.418164e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -9.174520e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -5.489617e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -3.878301e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -1.302233e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 1.054113e-05, ref tj, ref tj1, ref result);
+            ucheb(x, 2.458862e-05, ref tj, ref tj1, ref result);
+            ucheb(x, -4.186591e-06, ref tj, ref tj1, ref result);
+            ucheb(x, -2.623412e-05, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 6, 6)
+        *************************************************************************/
+        private static double utbln6n6(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/2.882307e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -3.054075e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -2.998804e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -6.681518e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -1.067578e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -1.709435e-02, ref tj, ref tj1, ref result);
+            ucheb(x, 9.952661e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 3.641700e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.304572e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 3.336275e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 4.770385e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 5.401891e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.246148e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.442663e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -2.502866e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -2.105855e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -4.739371e-04, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 6, 7)
+        *************************************************************************/
+        private static double utbln6n7(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.000000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -3.265287e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -3.274613e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -7.582352e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -1.334293e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -2.915502e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -4.108091e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 1.546701e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.298827e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.891501e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 4.313717e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 4.989501e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 3.914594e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 1.062372e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.158841e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.596443e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.185662e-03, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 6, 8)
+        *************************************************************************/
+        private static double utbln6n8(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.098387e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -3.450954e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -3.520462e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -8.420299e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -1.604853e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -4.165840e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -1.008756e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -6.723402e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 1.843521e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.883405e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 3.720980e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 4.301709e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 3.948034e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.776243e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 8.623736e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -3.742068e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -9.796927e-04, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 6, 9)
+        *************************************************************************/
+        private static double utbln6n9(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.181981e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -3.616113e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -3.741650e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -9.204487e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -1.873068e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -5.446794e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -1.632286e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -3.266481e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 1.280067e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.780687e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 3.480242e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 3.592200e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 3.581019e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 3.264231e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.347174e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 1.167535e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.092185e-04, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 6, 10)
+        *************************************************************************/
+        private static double utbln6n10(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.253957e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -3.764382e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -3.942366e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -9.939896e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -2.137812e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -6.720270e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -2.281070e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -5.901060e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 3.824937e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 2.802812e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 3.258132e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 3.233536e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 3.085530e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 3.212151e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 3.001329e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.226048e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 1.035298e-03, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 6, 11)
+        *************************************************************************/
+        private static double utbln6n11(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.316625e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -3.898597e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.125710e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.063297e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -2.396852e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -7.990126e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -2.927977e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -8.726500e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -5.858745e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 2.654590e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 3.217736e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.989770e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.768493e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.924364e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 3.140215e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.647914e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 1.924802e-03, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 6, 12)
+        *************************************************************************/
+        private static double utbln6n12(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.371709e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -4.020941e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.294250e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.128842e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -2.650389e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -9.248611e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -3.578510e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -1.162852e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -1.746982e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.454209e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 3.128042e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.936650e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.530794e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.665192e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.994144e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.662249e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.368541e-03, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 6, 13)
+        *************************************************************************/
+        private static double utbln6n13(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.420526e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -4.133167e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.450016e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.191088e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -2.898220e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -1.050249e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -4.226901e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -1.471113e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -3.007470e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.049420e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 3.059074e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.881249e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.452780e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.441805e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.787493e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.483957e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.481590e-03, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 6, 14)
+        *************************************************************************/
+        private static double utbln6n14(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.450000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -4.201268e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.542568e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.226965e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -3.046029e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -1.136657e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -4.786757e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -1.843748e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -5.588022e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.253029e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 1.667188e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 1.788330e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 1.474545e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 1.540494e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 1.951188e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 1.863323e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.220904e-03, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 6, 15)
+        *************************************************************************/
+        private static double utbln6n15(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.450000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -4.195689e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.526567e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.213617e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -2.975035e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -1.118480e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -4.859142e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -2.083312e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -8.298720e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -2.766708e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.026356e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -9.093113e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -1.135168e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.136376e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -8.190870e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -4.435972e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 1.413129e-04, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 6, 30)
+        *************************************************************************/
+        private static double utbln6n30(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.450000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -4.166269e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.427399e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.118239e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -2.360847e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -7.745885e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -3.025041e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -1.187179e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -4.432089e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.408451e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -4.388774e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -2.795560e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -2.304136e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -1.258516e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -4.180236e-05, ref tj, ref tj1, ref result);
+            ucheb(x, -4.388679e-06, ref tj, ref tj1, ref result);
+            ucheb(x, 4.836027e-06, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 6, 100)
+        *************************************************************************/
+        private static double utbln6n100(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.450000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -4.181350e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.417919e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.094201e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -2.195883e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -6.818937e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -2.514202e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -9.125047e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -3.022148e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -7.284181e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -1.157766e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -1.023752e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -1.127985e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -5.221690e-05, ref tj, ref tj1, ref result);
+            ucheb(x, -3.516179e-06, ref tj, ref tj1, ref result);
+            ucheb(x, 9.501398e-06, ref tj, ref tj1, ref result);
+            ucheb(x, 9.380220e-06, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 7, 7)
+        *************************************************************************/
+        private static double utbln7n7(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.130495e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -3.501264e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -3.584790e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -8.577311e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -1.617002e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -4.145186e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -1.023462e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -1.408251e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 8.626515e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 2.072492e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 3.722926e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 5.095445e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 4.842602e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.751427e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.008927e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -9.892431e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -8.772386e-04, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 7, 8)
+        *************************************************************************/
+        private static double utbln7n8(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.240370e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -3.709965e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -3.862154e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -9.504541e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -1.900195e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -5.439995e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -1.678028e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -4.485540e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -4.437047e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 1.440092e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 3.114227e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 4.516569e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 4.829457e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 3.787550e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 1.761866e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 1.991911e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -4.533481e-04, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 7, 9)
+        *************************************************************************/
+        private static double utbln7n9(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.334314e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -3.896550e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.112671e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.037277e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -2.181695e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -6.765190e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -2.360116e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -7.695960e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.780578e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 8.963843e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 2.616148e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 3.852104e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 4.390744e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 4.014041e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.888101e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 1.467474e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 4.004611e-04, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 7, 10)
+        *************************************************************************/
+        private static double utbln7n10(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.415650e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -4.064844e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.340749e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.118888e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -2.459730e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -8.097781e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -3.057688e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -1.097406e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -3.209262e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 4.065641e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 2.196677e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 3.313994e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 3.827157e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 3.822284e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 3.389090e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.340850e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 1.395172e-03, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 7, 11)
+        *************************************************************************/
+        private static double utbln7n11(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.486817e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -4.217795e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.549783e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.195905e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -2.733093e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -9.428447e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -3.760093e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -1.431676e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -4.717152e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.032199e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 1.832423e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.905979e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 3.302799e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 3.464371e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 3.456211e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.736244e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.140712e-03, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 7, 12)
+        *************************************************************************/
+        private static double utbln7n12(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.500000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -4.235822e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.564100e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.190813e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -2.686546e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -9.395083e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -3.967359e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -1.747096e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -8.304144e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -3.903198e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -2.134906e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.175035e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -7.266224e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -1.892931e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 5.604706e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 9.070459e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 1.427010e-03, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 7, 13)
+        *************************************************************************/
+        private static double utbln7n13(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.500000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -4.222204e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.532300e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.164642e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -2.523768e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -8.531984e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -3.467857e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -1.483804e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -6.524136e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -3.077740e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.745218e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.602085e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.828831e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.994070e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.873879e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.341937e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -8.706444e-04, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 7, 14)
+        *************************************************************************/
+        private static double utbln7n14(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.500000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -4.211763e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.507542e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.143640e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -2.395755e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -7.808020e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -3.044259e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -1.182308e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -4.057325e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -5.724255e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 8.303900e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 1.113148e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 8.102514e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 3.559442e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 4.634986e-05, ref tj, ref tj1, ref result);
+            ucheb(x, -8.776476e-05, ref tj, ref tj1, ref result);
+            ucheb(x, 1.054489e-05, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 7, 15)
+        *************************************************************************/
+        private static double utbln7n15(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.500000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -4.204898e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.489960e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.129172e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -2.316741e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -7.506107e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -2.983676e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -1.258013e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -5.262515e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.984156e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -3.912108e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 8.974023e-05, ref tj, ref tj1, ref result);
+            ucheb(x, 6.056195e-05, ref tj, ref tj1, ref result);
+            ucheb(x, -2.090842e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -5.232620e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -5.816339e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -7.020421e-04, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 7, 30)
+        *************************************************************************/
+        private static double utbln7n30(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.500000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -4.176536e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.398705e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.045481e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.821982e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -4.962304e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -1.698132e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -6.062667e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -2.282353e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -8.014836e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -2.035683e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -1.004137e-05, ref tj, ref tj1, ref result);
+            ucheb(x, 3.801453e-06, ref tj, ref tj1, ref result);
+            ucheb(x, -1.920705e-05, ref tj, ref tj1, ref result);
+            ucheb(x, -2.518735e-05, ref tj, ref tj1, ref result);
+            ucheb(x, -1.821501e-05, ref tj, ref tj1, ref result);
+            ucheb(x, -1.801008e-05, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 7, 100)
+        *************************************************************************/
+        private static double utbln7n100(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.500000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -4.188337e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.386949e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.022834e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.686517e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -4.323516e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -1.399392e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -4.644333e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.617044e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -5.031396e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -8.792066e-05, ref tj, ref tj1, ref result);
+            ucheb(x, 2.675457e-05, ref tj, ref tj1, ref result);
+            ucheb(x, 1.673416e-05, ref tj, ref tj1, ref result);
+            ucheb(x, -6.258552e-06, ref tj, ref tj1, ref result);
+            ucheb(x, -8.174214e-06, ref tj, ref tj1, ref result);
+            ucheb(x, -3.073644e-06, ref tj, ref tj1, ref result);
+            ucheb(x, -1.349958e-06, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 8, 8)
+        *************************************************************************/
+        private static double utbln8n8(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.360672e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -3.940217e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.168913e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.051485e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -2.195325e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -6.775196e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -2.385506e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -8.244902e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -2.525632e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.771275e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 2.332874e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 4.079599e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 4.882551e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 4.407944e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.769844e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 1.062433e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 5.872535e-05, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 8, 9)
+        *************************************************************************/
+        private static double utbln8n9(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.464102e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -4.147004e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.446939e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.146155e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -2.488561e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -8.144561e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -3.116917e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -1.205667e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -4.515661e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -7.618616e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 1.599011e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 3.457324e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 4.482917e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 4.488267e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 3.469823e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 1.957591e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 8.058326e-04, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 8, 10)
+        *************************************************************************/
+        private static double utbln8n10(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.554093e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -4.334282e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.700860e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.235253e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -2.778489e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -9.527324e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -3.862885e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -1.589781e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -6.507355e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.717526e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 9.215726e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 2.848696e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 3.918854e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 4.219614e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 3.753761e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.573688e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 1.602177e-03, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 8, 11)
+        *************************************************************************/
+        private static double utbln8n11(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.600000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -4.421882e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.812457e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.266153e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -2.849344e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -9.971527e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -4.258944e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -1.944820e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -9.894685e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -5.031836e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -2.514330e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -6.351660e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 6.206748e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 1.492600e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.005338e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 1.780099e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 1.673599e-03, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 8, 12)
+        *************************************************************************/
+        private static double utbln8n12(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.600000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -4.398211e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.762214e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.226296e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -2.603837e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -8.643223e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -3.502438e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -1.544574e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -7.647734e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -4.442259e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -3.011484e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -2.384758e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.998259e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.659985e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.331046e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -8.638478e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -6.056785e-04, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 8, 13)
+        *************************************************************************/
+        private static double utbln8n13(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.600000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -4.380670e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.724511e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.195851e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -2.420511e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -7.609928e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -2.893999e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -1.115919e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -4.291410e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.339664e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.801548e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 2.534710e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 2.793250e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 1.806718e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 1.384624e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 1.120582e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 2.936453e-04, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 8, 14)
+        *************************************************************************/
+        private static double utbln8n14(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.600000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -4.368494e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.697171e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.174440e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -2.300621e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -7.087393e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -2.685826e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -1.085254e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -4.525658e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.966647e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -7.453388e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -3.826066e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -3.501958e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -5.336297e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -8.251972e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -8.118456e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -9.415959e-04, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 8, 15)
+        *************************************************************************/
+        private static double utbln8n15(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.600000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -4.358397e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.674485e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.155941e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -2.195780e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -6.544830e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -2.426183e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -9.309902e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -3.650956e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.068874e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 1.538544e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 8.192525e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 1.073905e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 1.079673e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 9.423572e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 6.579647e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 4.765904e-04, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 8, 30)
+        *************************************************************************/
+        private static double utbln8n30(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.600000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -4.318823e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.567159e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.064864e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.688413e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -4.153712e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -1.309389e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -4.226861e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.523815e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -5.780987e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -2.166866e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -6.922431e-05, ref tj, ref tj1, ref result);
+            ucheb(x, -1.466397e-05, ref tj, ref tj1, ref result);
+            ucheb(x, -5.690036e-06, ref tj, ref tj1, ref result);
+            ucheb(x, -1.008185e-05, ref tj, ref tj1, ref result);
+            ucheb(x, -9.271903e-06, ref tj, ref tj1, ref result);
+            ucheb(x, -7.534751e-06, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 8, 100)
+        *************************************************************************/
+        private static double utbln8n100(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.600000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -4.324531e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.547071e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.038129e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.541549e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -3.525605e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -1.044992e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -3.085713e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.017871e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -3.459226e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -1.092064e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -2.024349e-05, ref tj, ref tj1, ref result);
+            ucheb(x, 7.366347e-06, ref tj, ref tj1, ref result);
+            ucheb(x, 6.385637e-06, ref tj, ref tj1, ref result);
+            ucheb(x, 8.321722e-08, ref tj, ref tj1, ref result);
+            ucheb(x, -1.439286e-06, ref tj, ref tj1, ref result);
+            ucheb(x, -3.058079e-07, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 9, 9)
+        *************************************************************************/
+        private static double utbln9n9(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.576237e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -4.372857e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.750859e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.248233e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -2.792868e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -9.559372e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -3.894941e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -1.643256e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -7.091370e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -2.285034e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 6.112997e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 2.806229e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 4.150741e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 4.509825e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 3.891051e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.485013e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 1.343653e-03, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 9, 10)
+        *************************************************************************/
+        private static double utbln9n10(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.650000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -4.516726e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.939333e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.305046e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -2.935326e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -1.029141e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -4.420592e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -2.053140e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -1.065930e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -5.523581e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -2.544888e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.813741e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 1.510631e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.536057e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.833815e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 2.189692e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 1.615050e-03, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 9, 11)
+        *************************************************************************/
+        private static double utbln9n11(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.650000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -4.481308e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.867483e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.249072e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -2.591790e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -8.400128e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -3.341992e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -1.463680e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -7.487211e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -4.671196e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -3.343472e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -2.544146e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.802335e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.117084e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -6.217443e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -2.858766e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -3.193687e-04, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 9, 12)
+        *************************************************************************/
+        private static double utbln9n12(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.650000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -4.456776e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.817037e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.209788e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -2.362108e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -7.171356e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -2.661557e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -1.026141e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -4.361908e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -2.093885e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.298389e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -9.663603e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -7.768522e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -5.579015e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -2.868677e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -7.440652e-05, ref tj, ref tj1, ref result);
+            ucheb(x, 1.523037e-04, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 9, 13)
+        *************************************************************************/
+        private static double utbln9n13(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.650000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -4.438840e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.779308e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.180614e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -2.196489e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -6.346621e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -2.234857e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -7.796211e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -2.575715e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -5.525647e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 1.964651e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 4.275235e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 4.299124e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 3.397416e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 2.295781e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 1.237619e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 7.269692e-05, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 9, 14)
+        *************************************************************************/
+        private static double utbln9n14(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.650000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -4.425981e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.751545e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.159543e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -2.086570e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -5.917446e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -2.120112e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -8.175519e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -3.515473e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.727772e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -9.070629e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -5.677569e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -3.876953e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -3.233502e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -3.508182e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -3.120389e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -3.847212e-04, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 9, 15)
+        *************************************************************************/
+        private static double utbln9n15(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.650000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -4.414952e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.727612e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.140634e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.981231e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -5.382635e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -1.853575e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -6.571051e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -2.567625e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -9.214197e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -2.448700e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 1.712669e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 4.015050e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 5.438610e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 6.301363e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 5.309386e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 5.164772e-04, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 9, 30)
+        *************************************************************************/
+        private static double utbln9n30(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.650000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -4.370720e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.615712e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.050023e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.504775e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -3.318265e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -9.646826e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -2.741492e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -8.735360e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -2.966911e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -1.100738e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -4.348991e-05, ref tj, ref tj1, ref result);
+            ucheb(x, -1.527687e-05, ref tj, ref tj1, ref result);
+            ucheb(x, -2.917286e-06, ref tj, ref tj1, ref result);
+            ucheb(x, 3.397466e-07, ref tj, ref tj1, ref result);
+            ucheb(x, -2.360175e-07, ref tj, ref tj1, ref result);
+            ucheb(x, -9.892252e-07, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 9, 100)
+        *************************************************************************/
+        private static double utbln9n100(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.650000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -4.372506e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.590966e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.021758e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.359849e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -2.755519e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -7.533166e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.936659e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -5.634913e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -1.730053e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -5.791845e-05, ref tj, ref tj1, ref result);
+            ucheb(x, -2.030682e-05, ref tj, ref tj1, ref result);
+            ucheb(x, -5.228663e-06, ref tj, ref tj1, ref result);
+            ucheb(x, 8.631175e-07, ref tj, ref tj1, ref result);
+            ucheb(x, 1.636749e-06, ref tj, ref tj1, ref result);
+            ucheb(x, 4.404599e-07, ref tj, ref tj1, ref result);
+            ucheb(x, -2.789872e-07, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 10, 10)
+        *************************************************************************/
+        private static double utbln10n10(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.650000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -4.468831e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.844398e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.231728e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -2.486073e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -7.781321e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -2.971425e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -1.215371e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -5.828451e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -3.419872e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -2.430165e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.740363e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.049211e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -3.269371e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 2.211393e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 4.232314e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 3.016081e-04, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 10, 11)
+        *************************************************************************/
+        private static double utbln10n11(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.650000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -4.437998e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.782296e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.184732e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -2.219585e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -6.457012e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -2.296008e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -8.481501e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -3.527940e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.953426e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.563840e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.574403e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.535775e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.338037e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.002654e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -5.852676e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -3.318132e-04, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 10, 12)
+        *************************************************************************/
+        private static double utbln10n12(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.650000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -4.416082e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.737458e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.150952e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -2.036884e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -5.609030e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -1.908684e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -6.439666e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -2.162647e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -6.451601e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -2.148757e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -1.803981e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -2.731621e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -3.346903e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -3.013151e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -1.956148e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -2.438381e-05, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 10, 13)
+        *************************************************************************/
+        private static double utbln10n13(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.650000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -4.399480e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.702863e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.124829e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.897428e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -4.979802e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -1.634368e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -5.180461e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.484926e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -7.864376e-05, ref tj, ref tj1, ref result);
+            ucheb(x, 4.186576e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 5.886925e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 5.836828e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 5.074756e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 4.209547e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 2.883266e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 2.380143e-04, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 10, 14)
+        *************************************************************************/
+        private static double utbln10n14(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.650000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -4.386924e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.676124e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.104740e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.793826e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -4.558886e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -1.492462e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -5.052903e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.917782e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -7.878696e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -3.576046e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -1.764551e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -9.288778e-05, ref tj, ref tj1, ref result);
+            ucheb(x, -4.757658e-05, ref tj, ref tj1, ref result);
+            ucheb(x, -2.299101e-05, ref tj, ref tj1, ref result);
+            ucheb(x, -9.265197e-06, ref tj, ref tj1, ref result);
+            ucheb(x, -2.384503e-07, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 10, 15)
+        *************************************************************************/
+        private static double utbln10n15(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.650000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -4.376846e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.654247e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.088083e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.705945e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -4.169677e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -1.317213e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -4.264836e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.548024e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -6.633910e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -3.505621e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -2.658588e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -2.320254e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -2.175277e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -2.122317e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -1.675688e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -1.661363e-04, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 10, 30)
+        *************************************************************************/
+        private static double utbln10n30(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.650000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -4.333977e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.548099e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.004444e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.291014e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -2.523674e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -6.828211e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.716917e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -4.894256e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -1.433371e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -4.522675e-05, ref tj, ref tj1, ref result);
+            ucheb(x, -1.764192e-05, ref tj, ref tj1, ref result);
+            ucheb(x, -9.140235e-06, ref tj, ref tj1, ref result);
+            ucheb(x, -5.629230e-06, ref tj, ref tj1, ref result);
+            ucheb(x, -3.541895e-06, ref tj, ref tj1, ref result);
+            ucheb(x, -1.944946e-06, ref tj, ref tj1, ref result);
+            ucheb(x, -1.726360e-06, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 10, 100)
+        *************************************************************************/
+        private static double utbln10n100(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.650000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -4.334008e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.522316e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -9.769627e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -1.158110e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -2.053650e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -5.242235e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.173571e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -3.033661e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -7.824732e-05, ref tj, ref tj1, ref result);
+            ucheb(x, -2.084420e-05, ref tj, ref tj1, ref result);
+            ucheb(x, -6.610036e-06, ref tj, ref tj1, ref result);
+            ucheb(x, -2.728155e-06, ref tj, ref tj1, ref result);
+            ucheb(x, -1.217130e-06, ref tj, ref tj1, ref result);
+            ucheb(x, -2.340966e-07, ref tj, ref tj1, ref result);
+            ucheb(x, 2.001235e-07, ref tj, ref tj1, ref result);
+            ucheb(x, 1.694052e-07, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 11, 11)
+        *************************************************************************/
+        private static double utbln11n11(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.700000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -4.519760e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.880694e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.200698e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -2.174092e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -6.072304e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -2.054773e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -6.506613e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.813942e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.223644e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 2.417416e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 2.499166e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 1.194332e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 7.369096e-05, ref tj, ref tj1, ref result);
+            ucheb(x, 1.968590e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 2.630532e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 5.061000e-04, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 11, 12)
+        *************************************************************************/
+        private static double utbln11n12(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.700000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -4.495790e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.832622e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.165420e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.987306e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -5.265621e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -1.723537e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -5.347406e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.353464e-03, ref tj, ref tj1, ref result);
+            ucheb(x, 6.613369e-05, ref tj, ref tj1, ref result);
+            ucheb(x, 5.102522e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 5.237709e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 3.665652e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 1.626903e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -1.167518e-05, ref tj, ref tj1, ref result);
+            ucheb(x, -8.564455e-05, ref tj, ref tj1, ref result);
+            ucheb(x, -1.047320e-04, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 11, 13)
+        *************************************************************************/
+        private static double utbln11n13(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.700000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -4.477880e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.796242e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.138769e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.851739e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -4.722104e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -1.548304e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -5.176683e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.817895e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -5.842451e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -8.935870e-05, ref tj, ref tj1, ref result);
+            ucheb(x, 8.421777e-05, ref tj, ref tj1, ref result);
+            ucheb(x, 1.238831e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 8.867026e-05, ref tj, ref tj1, ref result);
+            ucheb(x, 1.458255e-05, ref tj, ref tj1, ref result);
+            ucheb(x, -3.306259e-05, ref tj, ref tj1, ref result);
+            ucheb(x, -8.961487e-05, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 11, 14)
+        *************************************************************************/
+        private static double utbln11n14(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.700000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -4.463683e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.766969e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.117082e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.739574e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -4.238865e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -1.350306e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -4.425871e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.640172e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -6.660633e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -2.879883e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -1.349658e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -6.271795e-05, ref tj, ref tj1, ref result);
+            ucheb(x, -3.304544e-05, ref tj, ref tj1, ref result);
+            ucheb(x, -3.024201e-05, ref tj, ref tj1, ref result);
+            ucheb(x, -2.816867e-05, ref tj, ref tj1, ref result);
+            ucheb(x, -4.596787e-05, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 11, 15)
+        *************************************************************************/
+        private static double utbln11n15(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.700000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -4.452526e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.743570e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.099705e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.650612e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -3.858285e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -1.187036e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -3.689241e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.294360e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -5.072623e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -2.278008e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -1.322382e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -9.131558e-05, ref tj, ref tj1, ref result);
+            ucheb(x, -7.305669e-05, ref tj, ref tj1, ref result);
+            ucheb(x, -6.825627e-05, ref tj, ref tj1, ref result);
+            ucheb(x, -5.332689e-05, ref tj, ref tj1, ref result);
+            ucheb(x, -6.120973e-05, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 11, 30)
+        *************************************************************************/
+        private static double utbln11n30(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.700000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -4.402621e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.627440e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.011333e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.224126e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -2.232856e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -5.859347e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.377381e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -3.756709e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -1.033230e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -2.875472e-05, ref tj, ref tj1, ref result);
+            ucheb(x, -8.608399e-06, ref tj, ref tj1, ref result);
+            ucheb(x, -3.102943e-06, ref tj, ref tj1, ref result);
+            ucheb(x, -1.740693e-06, ref tj, ref tj1, ref result);
+            ucheb(x, -1.343139e-06, ref tj, ref tj1, ref result);
+            ucheb(x, -9.196878e-07, ref tj, ref tj1, ref result);
+            ucheb(x, -6.658062e-07, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 11, 100)
+        *************************************************************************/
+        private static double utbln11n100(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.700000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -4.398795e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.596486e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -9.814761e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -1.085187e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -1.766529e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -4.379425e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -8.986351e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -2.214705e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -5.360075e-05, ref tj, ref tj1, ref result);
+            ucheb(x, -1.260869e-05, ref tj, ref tj1, ref result);
+            ucheb(x, -3.033307e-06, ref tj, ref tj1, ref result);
+            ucheb(x, -7.727087e-07, ref tj, ref tj1, ref result);
+            ucheb(x, -3.393883e-07, ref tj, ref tj1, ref result);
+            ucheb(x, -2.242989e-07, ref tj, ref tj1, ref result);
+            ucheb(x, -1.111928e-07, ref tj, ref tj1, ref result);
+            ucheb(x, 3.898823e-09, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 12, 12)
+        *************************************************************************/
+        private static double utbln12n12(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.700000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -4.472616e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.786627e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.132099e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.817523e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -4.570179e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -1.479511e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -4.799492e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.565350e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -3.530139e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 1.380132e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 3.242761e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 3.576269e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 3.018771e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 1.933911e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 9.002799e-05, ref tj, ref tj1, ref result);
+            ucheb(x, -2.022048e-06, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 12, 13)
+        *************************************************************************/
+        private static double utbln12n13(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.700000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -4.454800e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.750794e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.105988e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.684754e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -4.011826e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -1.262579e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -4.044492e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.478741e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -5.322165e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -1.621104e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 4.068753e-05, ref tj, ref tj1, ref result);
+            ucheb(x, 1.468396e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 2.056235e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 2.327375e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 1.914877e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 1.784191e-04, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 12, 14)
+        *************************************************************************/
+        private static double utbln12n14(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.700000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -4.440910e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.722404e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.085254e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.579439e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -3.563738e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -1.066730e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -3.129346e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.014531e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -3.129679e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -8.000909e-05, ref tj, ref tj1, ref result);
+            ucheb(x, 1.996174e-05, ref tj, ref tj1, ref result);
+            ucheb(x, 6.377924e-05, ref tj, ref tj1, ref result);
+            ucheb(x, 8.936304e-05, ref tj, ref tj1, ref result);
+            ucheb(x, 1.051098e-04, ref tj, ref tj1, ref result);
+            ucheb(x, 9.025820e-05, ref tj, ref tj1, ref result);
+            ucheb(x, 8.730585e-05, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 12, 15)
+        *************************************************************************/
+        private static double utbln12n15(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.700000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -4.430123e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.700008e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.068971e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.499725e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -3.250897e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -9.473145e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -2.680008e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -8.483350e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -2.766992e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -9.891081e-05, ref tj, ref tj1, ref result);
+            ucheb(x, -4.015140e-05, ref tj, ref tj1, ref result);
+            ucheb(x, -1.977756e-05, ref tj, ref tj1, ref result);
+            ucheb(x, -8.707414e-06, ref tj, ref tj1, ref result);
+            ucheb(x, 1.114786e-06, ref tj, ref tj1, ref result);
+            ucheb(x, 6.238865e-06, ref tj, ref tj1, ref result);
+            ucheb(x, 1.381445e-05, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 12, 30)
+        *************************************************************************/
+        private static double utbln12n30(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.700000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -4.380023e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.585782e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -9.838583e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -1.103394e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -1.834015e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -4.635212e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -9.948212e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -2.574169e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -6.747980e-05, ref tj, ref tj1, ref result);
+            ucheb(x, -1.833672e-05, ref tj, ref tj1, ref result);
+            ucheb(x, -5.722433e-06, ref tj, ref tj1, ref result);
+            ucheb(x, -2.181038e-06, ref tj, ref tj1, ref result);
+            ucheb(x, -1.206473e-06, ref tj, ref tj1, ref result);
+            ucheb(x, -9.716003e-07, ref tj, ref tj1, ref result);
+            ucheb(x, -7.476434e-07, ref tj, ref tj1, ref result);
+            ucheb(x, -7.217700e-07, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 12, 100)
+        *************************************************************************/
+        private static double utbln12n100(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.700000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -4.374567e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.553481e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -9.541334e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -9.701907e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -1.414757e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -3.404103e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -6.234388e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -1.453762e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -3.311060e-05, ref tj, ref tj1, ref result);
+            ucheb(x, -7.317501e-06, ref tj, ref tj1, ref result);
+            ucheb(x, -1.713888e-06, ref tj, ref tj1, ref result);
+            ucheb(x, -3.309583e-07, ref tj, ref tj1, ref result);
+            ucheb(x, -4.019804e-08, ref tj, ref tj1, ref result);
+            ucheb(x, 1.224829e-09, ref tj, ref tj1, ref result);
+            ucheb(x, -1.349019e-08, ref tj, ref tj1, ref result);
+            ucheb(x, -1.893302e-08, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 13, 13)
+        *************************************************************************/
+        private static double utbln13n13(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.750000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -4.541046e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.859047e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.130164e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.689719e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -3.950693e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -1.231455e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -3.976550e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -1.538455e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -7.245603e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -4.142647e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -2.831434e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -2.032483e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -1.488405e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -1.156927e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -7.949279e-05, ref tj, ref tj1, ref result);
+            ucheb(x, -7.532700e-05, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 13, 14)
+        *************************************************************************/
+        private static double utbln13n14(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.750000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -4.525655e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.828341e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.108110e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.579552e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -3.488307e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -1.032328e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -2.988741e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -9.766394e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -3.388950e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -1.338179e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -6.133440e-05, ref tj, ref tj1, ref result);
+            ucheb(x, -3.023518e-05, ref tj, ref tj1, ref result);
+            ucheb(x, -1.110570e-05, ref tj, ref tj1, ref result);
+            ucheb(x, 4.202332e-06, ref tj, ref tj1, ref result);
+            ucheb(x, 1.056132e-05, ref tj, ref tj1, ref result);
+            ucheb(x, 1.536323e-05, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 13, 15)
+        *************************************************************************/
+        private static double utbln13n15(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.750000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -4.513585e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.803952e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.090686e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.495310e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -3.160314e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -9.073124e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -2.480313e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -7.478239e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -2.140914e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -5.311541e-05, ref tj, ref tj1, ref result);
+            ucheb(x, -2.677105e-06, ref tj, ref tj1, ref result);
+            ucheb(x, 1.115464e-05, ref tj, ref tj1, ref result);
+            ucheb(x, 1.578563e-05, ref tj, ref tj1, ref result);
+            ucheb(x, 2.044604e-05, ref tj, ref tj1, ref result);
+            ucheb(x, 1.888939e-05, ref tj, ref tj1, ref result);
+            ucheb(x, 2.395644e-05, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 13, 30)
+        *************************************************************************/
+        private static double utbln13n30(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.750000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -4.455999e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.678434e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -9.995491e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -1.078100e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -1.705220e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -4.258739e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -8.671526e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -2.185458e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -5.507764e-05, ref tj, ref tj1, ref result);
+            ucheb(x, -1.411446e-05, ref tj, ref tj1, ref result);
+            ucheb(x, -4.044355e-06, ref tj, ref tj1, ref result);
+            ucheb(x, -1.285765e-06, ref tj, ref tj1, ref result);
+            ucheb(x, -5.345282e-07, ref tj, ref tj1, ref result);
+            ucheb(x, -3.066940e-07, ref tj, ref tj1, ref result);
+            ucheb(x, -1.962037e-07, ref tj, ref tj1, ref result);
+            ucheb(x, -1.723644e-07, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 13, 100)
+        *************************************************************************/
+        private static double utbln13n100(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.750000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -4.446787e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.640804e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -9.671552e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -9.364990e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -1.274444e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -3.047440e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -5.161439e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -1.171729e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -2.562171e-05, ref tj, ref tj1, ref result);
+            ucheb(x, -5.359762e-06, ref tj, ref tj1, ref result);
+            ucheb(x, -1.275494e-06, ref tj, ref tj1, ref result);
+            ucheb(x, -2.747635e-07, ref tj, ref tj1, ref result);
+            ucheb(x, -5.700292e-08, ref tj, ref tj1, ref result);
+            ucheb(x, -2.565559e-09, ref tj, ref tj1, ref result);
+            ucheb(x, 5.005396e-09, ref tj, ref tj1, ref result);
+            ucheb(x, 3.335794e-09, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 14, 14)
+        *************************************************************************/
+        private static double utbln14n14(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.750000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -4.510624e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.798584e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.087107e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.478532e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -3.098050e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -8.855986e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -2.409083e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -7.299536e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -2.176177e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -6.479417e-05, ref tj, ref tj1, ref result);
+            ucheb(x, -1.812761e-05, ref tj, ref tj1, ref result);
+            ucheb(x, -5.225872e-06, ref tj, ref tj1, ref result);
+            ucheb(x, 4.516521e-07, ref tj, ref tj1, ref result);
+            ucheb(x, 6.730551e-06, ref tj, ref tj1, ref result);
+            ucheb(x, 9.237563e-06, ref tj, ref tj1, ref result);
+            ucheb(x, 1.611820e-05, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 14, 15)
+        *************************************************************************/
+        private static double utbln14n15(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.750000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -4.498681e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.774668e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.070267e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -1.399348e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -2.807239e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -7.845763e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -2.071773e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -6.261698e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -2.011695e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -7.305946e-05, ref tj, ref tj1, ref result);
+            ucheb(x, -3.879295e-05, ref tj, ref tj1, ref result);
+            ucheb(x, -2.999439e-05, ref tj, ref tj1, ref result);
+            ucheb(x, -2.904438e-05, ref tj, ref tj1, ref result);
+            ucheb(x, -2.944986e-05, ref tj, ref tj1, ref result);
+            ucheb(x, -2.373908e-05, ref tj, ref tj1, ref result);
+            ucheb(x, -2.140794e-05, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 14, 30)
+        *************************************************************************/
+        private static double utbln14n30(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.750000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -4.440378e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.649587e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -9.807829e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -9.989753e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -1.463646e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -3.586580e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -6.745917e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -1.635398e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -3.923172e-05, ref tj, ref tj1, ref result);
+            ucheb(x, -9.446699e-06, ref tj, ref tj1, ref result);
+            ucheb(x, -2.613892e-06, ref tj, ref tj1, ref result);
+            ucheb(x, -8.214073e-07, ref tj, ref tj1, ref result);
+            ucheb(x, -3.651683e-07, ref tj, ref tj1, ref result);
+            ucheb(x, -2.272777e-07, ref tj, ref tj1, ref result);
+            ucheb(x, -1.464988e-07, ref tj, ref tj1, ref result);
+            ucheb(x, -1.109803e-07, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 14, 100)
+        *************************************************************************/
+        private static double utbln14n100(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/3.750000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            ucheb(x, -4.429701e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -4.610577e+00, ref tj, ref tj1, ref result);
+            ucheb(x, -9.482675e-01, ref tj, ref tj1, ref result);
+            ucheb(x, -8.605550e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -1.062151e-02, ref tj, ref tj1, ref result);
+            ucheb(x, -2.525154e-03, ref tj, ref tj1, ref result);
+            ucheb(x, -3.835983e-04, ref tj, ref tj1, ref result);
+            ucheb(x, -8.411440e-05, ref tj, ref tj1, ref result);
+            ucheb(x, -1.744901e-05, ref tj, ref tj1, ref result);
+            ucheb(x, -3.318850e-06, ref tj, ref tj1, ref result);
+            ucheb(x, -7.692100e-07, ref tj, ref tj1, ref result);
+            ucheb(x, -1.536270e-07, ref tj, ref tj1, ref result);
+            ucheb(x, -3.705888e-08, ref tj, ref tj1, ref result);
+            ucheb(x, -7.999599e-09, ref tj, ref tj1, ref result);
+            ucheb(x, -2.908395e-09, ref tj, ref tj1, ref result);
+            ucheb(x, 1.546923e-09, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, N1, N2)
+        *************************************************************************/
+        private static double usigma(double s,
+            int n1,
+            int n2)
+        {
+            double result = 0;
+            double f0 = 0;
+            double f1 = 0;
+            double f2 = 0;
+            double f3 = 0;
+            double f4 = 0;
+            double s0 = 0;
+            double s1 = 0;
+            double s2 = 0;
+            double s3 = 0;
+            double s4 = 0;
+
+            result = 0;
+            
+            //
+            // N1=5, N2 = 5, 6, 7, ...
+            //
+            if( Math.Min(n1, n2)==5 )
+            {
+                if( Math.Max(n1, n2)==5 )
+                {
+                    result = utbln5n5(s);
+                }
+                if( Math.Max(n1, n2)==6 )
+                {
+                    result = utbln5n6(s);
+                }
+                if( Math.Max(n1, n2)==7 )
+                {
+                    result = utbln5n7(s);
+                }
+                if( Math.Max(n1, n2)==8 )
+                {
+                    result = utbln5n8(s);
+                }
+                if( Math.Max(n1, n2)==9 )
+                {
+                    result = utbln5n9(s);
+                }
+                if( Math.Max(n1, n2)==10 )
+                {
+                    result = utbln5n10(s);
+                }
+                if( Math.Max(n1, n2)==11 )
+                {
+                    result = utbln5n11(s);
+                }
+                if( Math.Max(n1, n2)==12 )
+                {
+                    result = utbln5n12(s);
+                }
+                if( Math.Max(n1, n2)==13 )
+                {
+                    result = utbln5n13(s);
+                }
+                if( Math.Max(n1, n2)==14 )
+                {
+                    result = utbln5n14(s);
+                }
+                if( Math.Max(n1, n2)==15 )
+                {
+                    result = utbln5n15(s);
+                }
+                if( Math.Max(n1, n2)==16 )
+                {
+                    result = utbln5n16(s);
+                }
+                if( Math.Max(n1, n2)==17 )
+                {
+                    result = utbln5n17(s);
+                }
+                if( Math.Max(n1, n2)==18 )
+                {
+                    result = utbln5n18(s);
+                }
+                if( Math.Max(n1, n2)==19 )
+                {
+                    result = utbln5n19(s);
+                }
+                if( Math.Max(n1, n2)==20 )
+                {
+                    result = utbln5n20(s);
+                }
+                if( Math.Max(n1, n2)==21 )
+                {
+                    result = utbln5n21(s);
+                }
+                if( Math.Max(n1, n2)==22 )
+                {
+                    result = utbln5n22(s);
+                }
+                if( Math.Max(n1, n2)==23 )
+                {
+                    result = utbln5n23(s);
+                }
+                if( Math.Max(n1, n2)==24 )
+                {
+                    result = utbln5n24(s);
+                }
+                if( Math.Max(n1, n2)==25 )
+                {
+                    result = utbln5n25(s);
+                }
+                if( Math.Max(n1, n2)==26 )
+                {
+                    result = utbln5n26(s);
+                }
+                if( Math.Max(n1, n2)==27 )
+                {
+                    result = utbln5n27(s);
+                }
+                if( Math.Max(n1, n2)==28 )
+                {
+                    result = utbln5n28(s);
+                }
+                if( Math.Max(n1, n2)==29 )
+                {
+                    result = utbln5n29(s);
+                }
+                if( Math.Max(n1, n2)>29 )
+                {
+                    f0 = utbln5n15(s);
+                    f1 = utbln5n30(s);
+                    f2 = utbln5n100(s);
+                    result = uninterpolate(f0, f1, f2, Math.Max(n1, n2));
+                }
+                return result;
+            }
+            
+            //
+            // N1=6, N2 = 6, 7, 8, ...
+            //
+            if( Math.Min(n1, n2)==6 )
+            {
+                if( Math.Max(n1, n2)==6 )
+                {
+                    result = utbln6n6(s);
+                }
+                if( Math.Max(n1, n2)==7 )
+                {
+                    result = utbln6n7(s);
+                }
+                if( Math.Max(n1, n2)==8 )
+                {
+                    result = utbln6n8(s);
+                }
+                if( Math.Max(n1, n2)==9 )
+                {
+                    result = utbln6n9(s);
+                }
+                if( Math.Max(n1, n2)==10 )
+                {
+                    result = utbln6n10(s);
+                }
+                if( Math.Max(n1, n2)==11 )
+                {
+                    result = utbln6n11(s);
+                }
+                if( Math.Max(n1, n2)==12 )
+                {
+                    result = utbln6n12(s);
+                }
+                if( Math.Max(n1, n2)==13 )
+                {
+                    result = utbln6n13(s);
+                }
+                if( Math.Max(n1, n2)==14 )
+                {
+                    result = utbln6n14(s);
+                }
+                if( Math.Max(n1, n2)==15 )
+                {
+                    result = utbln6n15(s);
+                }
+                if( Math.Max(n1, n2)>15 )
+                {
+                    f0 = utbln6n15(s);
+                    f1 = utbln6n30(s);
+                    f2 = utbln6n100(s);
+                    result = uninterpolate(f0, f1, f2, Math.Max(n1, n2));
+                }
+                return result;
+            }
+            
+            //
+            // N1=7, N2 = 7, 8, ...
+            //
+            if( Math.Min(n1, n2)==7 )
+            {
+                if( Math.Max(n1, n2)==7 )
+                {
+                    result = utbln7n7(s);
+                }
+                if( Math.Max(n1, n2)==8 )
+                {
+                    result = utbln7n8(s);
+                }
+                if( Math.Max(n1, n2)==9 )
+                {
+                    result = utbln7n9(s);
+                }
+                if( Math.Max(n1, n2)==10 )
+                {
+                    result = utbln7n10(s);
+                }
+                if( Math.Max(n1, n2)==11 )
+                {
+                    result = utbln7n11(s);
+                }
+                if( Math.Max(n1, n2)==12 )
+                {
+                    result = utbln7n12(s);
+                }
+                if( Math.Max(n1, n2)==13 )
+                {
+                    result = utbln7n13(s);
+                }
+                if( Math.Max(n1, n2)==14 )
+                {
+                    result = utbln7n14(s);
+                }
+                if( Math.Max(n1, n2)==15 )
+                {
+                    result = utbln7n15(s);
+                }
+                if( Math.Max(n1, n2)>15 )
+                {
+                    f0 = utbln7n15(s);
+                    f1 = utbln7n30(s);
+                    f2 = utbln7n100(s);
+                    result = uninterpolate(f0, f1, f2, Math.Max(n1, n2));
+                }
+                return result;
+            }
+            
+            //
+            // N1=8, N2 = 8, 9, 10, ...
+            //
+            if( Math.Min(n1, n2)==8 )
+            {
+                if( Math.Max(n1, n2)==8 )
+                {
+                    result = utbln8n8(s);
+                }
+                if( Math.Max(n1, n2)==9 )
+                {
+                    result = utbln8n9(s);
+                }
+                if( Math.Max(n1, n2)==10 )
+                {
+                    result = utbln8n10(s);
+                }
+                if( Math.Max(n1, n2)==11 )
+                {
+                    result = utbln8n11(s);
+                }
+                if( Math.Max(n1, n2)==12 )
+                {
+                    result = utbln8n12(s);
+                }
+                if( Math.Max(n1, n2)==13 )
+                {
+                    result = utbln8n13(s);
+                }
+                if( Math.Max(n1, n2)==14 )
+                {
+                    result = utbln8n14(s);
+                }
+                if( Math.Max(n1, n2)==15 )
+                {
+                    result = utbln8n15(s);
+                }
+                if( Math.Max(n1, n2)>15 )
+                {
+                    f0 = utbln8n15(s);
+                    f1 = utbln8n30(s);
+                    f2 = utbln8n100(s);
+                    result = uninterpolate(f0, f1, f2, Math.Max(n1, n2));
+                }
+                return result;
+            }
+            
+            //
+            // N1=9, N2 = 9, 10, ...
+            //
+            if( Math.Min(n1, n2)==9 )
+            {
+                if( Math.Max(n1, n2)==9 )
+                {
+                    result = utbln9n9(s);
+                }
+                if( Math.Max(n1, n2)==10 )
+                {
+                    result = utbln9n10(s);
+                }
+                if( Math.Max(n1, n2)==11 )
+                {
+                    result = utbln9n11(s);
+                }
+                if( Math.Max(n1, n2)==12 )
+                {
+                    result = utbln9n12(s);
+                }
+                if( Math.Max(n1, n2)==13 )
+                {
+                    result = utbln9n13(s);
+                }
+                if( Math.Max(n1, n2)==14 )
+                {
+                    result = utbln9n14(s);
+                }
+                if( Math.Max(n1, n2)==15 )
+                {
+                    result = utbln9n15(s);
+                }
+                if( Math.Max(n1, n2)>15 )
+                {
+                    f0 = utbln9n15(s);
+                    f1 = utbln9n30(s);
+                    f2 = utbln9n100(s);
+                    result = uninterpolate(f0, f1, f2, Math.Max(n1, n2));
+                }
+                return result;
+            }
+            
+            //
+            // N1=10, N2 = 10, 11, ...
+            //
+            if( Math.Min(n1, n2)==10 )
+            {
+                if( Math.Max(n1, n2)==10 )
+                {
+                    result = utbln10n10(s);
+                }
+                if( Math.Max(n1, n2)==11 )
+                {
+                    result = utbln10n11(s);
+                }
+                if( Math.Max(n1, n2)==12 )
+                {
+                    result = utbln10n12(s);
+                }
+                if( Math.Max(n1, n2)==13 )
+                {
+                    result = utbln10n13(s);
+                }
+                if( Math.Max(n1, n2)==14 )
+                {
+                    result = utbln10n14(s);
+                }
+                if( Math.Max(n1, n2)==15 )
+                {
+                    result = utbln10n15(s);
+                }
+                if( Math.Max(n1, n2)>15 )
+                {
+                    f0 = utbln10n15(s);
+                    f1 = utbln10n30(s);
+                    f2 = utbln10n100(s);
+                    result = uninterpolate(f0, f1, f2, Math.Max(n1, n2));
+                }
+                return result;
+            }
+            
+            //
+            // N1=11, N2 = 11, 12, ...
+            //
+            if( Math.Min(n1, n2)==11 )
+            {
+                if( Math.Max(n1, n2)==11 )
+                {
+                    result = utbln11n11(s);
+                }
+                if( Math.Max(n1, n2)==12 )
+                {
+                    result = utbln11n12(s);
+                }
+                if( Math.Max(n1, n2)==13 )
+                {
+                    result = utbln11n13(s);
+                }
+                if( Math.Max(n1, n2)==14 )
+                {
+                    result = utbln11n14(s);
+                }
+                if( Math.Max(n1, n2)==15 )
+                {
+                    result = utbln11n15(s);
+                }
+                if( Math.Max(n1, n2)>15 )
+                {
+                    f0 = utbln11n15(s);
+                    f1 = utbln11n30(s);
+                    f2 = utbln11n100(s);
+                    result = uninterpolate(f0, f1, f2, Math.Max(n1, n2));
+                }
+                return result;
+            }
+            
+            //
+            // N1=12, N2 = 12, 13, ...
+            //
+            if( Math.Min(n1, n2)==12 )
+            {
+                if( Math.Max(n1, n2)==12 )
+                {
+                    result = utbln12n12(s);
+                }
+                if( Math.Max(n1, n2)==13 )
+                {
+                    result = utbln12n13(s);
+                }
+                if( Math.Max(n1, n2)==14 )
+                {
+                    result = utbln12n14(s);
+                }
+                if( Math.Max(n1, n2)==15 )
+                {
+                    result = utbln12n15(s);
+                }
+                if( Math.Max(n1, n2)>15 )
+                {
+                    f0 = utbln12n15(s);
+                    f1 = utbln12n30(s);
+                    f2 = utbln12n100(s);
+                    result = uninterpolate(f0, f1, f2, Math.Max(n1, n2));
+                }
+                return result;
+            }
+            
+            //
+            // N1=13, N2 = 13, 14, ...
+            //
+            if( Math.Min(n1, n2)==13 )
+            {
+                if( Math.Max(n1, n2)==13 )
+                {
+                    result = utbln13n13(s);
+                }
+                if( Math.Max(n1, n2)==14 )
+                {
+                    result = utbln13n14(s);
+                }
+                if( Math.Max(n1, n2)==15 )
+                {
+                    result = utbln13n15(s);
+                }
+                if( Math.Max(n1, n2)>15 )
+                {
+                    f0 = utbln13n15(s);
+                    f1 = utbln13n30(s);
+                    f2 = utbln13n100(s);
+                    result = uninterpolate(f0, f1, f2, Math.Max(n1, n2));
+                }
+                return result;
+            }
+            
+            //
+            // N1=14, N2 = 14, 15, ...
+            //
+            if( Math.Min(n1, n2)==14 )
+            {
+                if( Math.Max(n1, n2)==14 )
+                {
+                    result = utbln14n14(s);
+                }
+                if( Math.Max(n1, n2)==15 )
+                {
+                    result = utbln14n15(s);
+                }
+                if( Math.Max(n1, n2)>15 )
+                {
+                    f0 = utbln14n15(s);
+                    f1 = utbln14n30(s);
+                    f2 = utbln14n100(s);
+                    result = uninterpolate(f0, f1, f2, Math.Max(n1, n2));
+                }
+                return result;
+            }
+            
+            //
+            // N1 >= 15, N2 >= 15
+            //
+            if( (double)(s)>(double)(4) )
+            {
+                s = 4;
+            }
+            if( (double)(s)<(double)(3) )
+            {
+                s0 = 0.000000e+00;
+                f0 = usigma000(n1, n2);
+                s1 = 7.500000e-01;
+                f1 = usigma075(n1, n2);
+                s2 = 1.500000e+00;
+                f2 = usigma150(n1, n2);
+                s3 = 2.250000e+00;
+                f3 = usigma225(n1, n2);
+                s4 = 3.000000e+00;
+                f4 = usigma300(n1, n2);
+                f1 = ((s-s0)*f1-(s-s1)*f0)/(s1-s0);
+                f2 = ((s-s0)*f2-(s-s2)*f0)/(s2-s0);
+                f3 = ((s-s0)*f3-(s-s3)*f0)/(s3-s0);
+                f4 = ((s-s0)*f4-(s-s4)*f0)/(s4-s0);
+                f2 = ((s-s1)*f2-(s-s2)*f1)/(s2-s1);
+                f3 = ((s-s1)*f3-(s-s3)*f1)/(s3-s1);
+                f4 = ((s-s1)*f4-(s-s4)*f1)/(s4-s1);
+                f3 = ((s-s2)*f3-(s-s3)*f2)/(s3-s2);
+                f4 = ((s-s2)*f4-(s-s4)*f2)/(s4-s2);
+                f4 = ((s-s3)*f4-(s-s4)*f3)/(s4-s3);
+                result = f4;
+            }
+            else
+            {
+                s0 = 3.000000e+00;
+                f0 = usigma300(n1, n2);
+                s1 = 3.333333e+00;
+                f1 = usigma333(n1, n2);
+                s2 = 3.666667e+00;
+                f2 = usigma367(n1, n2);
+                s3 = 4.000000e+00;
+                f3 = usigma400(n1, n2);
+                f1 = ((s-s0)*f1-(s-s1)*f0)/(s1-s0);
+                f2 = ((s-s0)*f2-(s-s2)*f0)/(s2-s0);
+                f3 = ((s-s0)*f3-(s-s3)*f0)/(s3-s0);
+                f2 = ((s-s1)*f2-(s-s2)*f1)/(s2-s1);
+                f3 = ((s-s1)*f3-(s-s3)*f1)/(s3-s1);
+                f3 = ((s-s2)*f3-(s-s3)*f2)/(s3-s2);
+                result = f3;
+            }
+            return result;
+        }
+
+
+    }
+    public class stest
+    {
+        /*************************************************************************
+        Sign test
+
+        This test checks three hypotheses about the median of  the  given  sample.
+        The following tests are performed:
+            * two-tailed test (null hypothesis - the median is equal to the  given
+              value)
+            * left-tailed test (null hypothesis - the median is  greater  than  or
+              equal to the given value)
+            * right-tailed test (null hypothesis - the  median  is  less  than  or
+              equal to the given value)
+
+        Requirements:
+            * the scale of measurement should be ordinal, interval or ratio  (i.e.
+              the test could not be applied to nominal variables).
+
+        The test is non-parametric and doesn't require distribution X to be normal
+
+        Input parameters:
+            X       -   sample. Array whose index goes from 0 to N-1.
+            N       -   size of the sample.
+            Median  -   assumed median value.
+
+        Output parameters:
+            BothTails   -   p-value for two-tailed test.
+                            If BothTails is less than the given significance level
+                            the null hypothesis is rejected.
+            LeftTail    -   p-value for left-tailed test.
+                            If LeftTail is less than the given significance level,
+                            the null hypothesis is rejected.
+            RightTail   -   p-value for right-tailed test.
+                            If RightTail is less than the given significance level
+                            the null hypothesis is rejected.
+
+        While   calculating   p-values   high-precision   binomial    distribution
+        approximation is used, so significance levels have about 15 exact digits.
+
+          -- ALGLIB --
+             Copyright 08.09.2006 by Bochkanov Sergey
+        *************************************************************************/
+        public static void onesamplesigntest(double[] x,
+            int n,
+            double median,
+            ref double bothtails,
+            ref double lefttail,
+            ref double righttail)
+        {
+            int i = 0;
+            int gtcnt = 0;
+            int necnt = 0;
+
+            bothtails = 0;
+            lefttail = 0;
+            righttail = 0;
+
+            if( n<=1 )
+            {
+                bothtails = 1.0;
+                lefttail = 1.0;
+                righttail = 1.0;
+                return;
+            }
+            
+            //
+            // Calculate:
+            // GTCnt - count of x[i]>Median
+            // NECnt - count of x[i]<>Median
+            //
+            gtcnt = 0;
+            necnt = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                if( (double)(x[i])>(double)(median) )
+                {
+                    gtcnt = gtcnt+1;
+                }
+                if( (double)(x[i])!=(double)(median) )
+                {
+                    necnt = necnt+1;
+                }
+            }
+            if( necnt==0 )
+            {
+                
+                //
+                // all x[i] are equal to Median.
+                // So we can conclude that Median is a true median :)
+                //
+                bothtails = 0.0;
+                lefttail = 0.0;
+                righttail = 0.0;
+                return;
+            }
+            bothtails = 2*binomialdistr.binomialdistribution(Math.Min(gtcnt, necnt-gtcnt), necnt, 0.5);
+            lefttail = binomialdistr.binomialdistribution(gtcnt, necnt, 0.5);
+            righttail = binomialdistr.binomialcdistribution(gtcnt-1, necnt, 0.5);
+        }
+
+
+    }
+    public class studentttests
+    {
+        /*************************************************************************
+        One-sample t-test
+
+        This test checks three hypotheses about the mean of the given sample.  The
+        following tests are performed:
+            * two-tailed test (null hypothesis - the mean is equal  to  the  given
+              value)
+            * left-tailed test (null hypothesis - the  mean  is  greater  than  or
+              equal to the given value)
+            * right-tailed test (null hypothesis - the mean is less than or  equal
+              to the given value).
+
+        The test is based on the assumption that  a  given  sample  has  a  normal
+        distribution and  an  unknown  dispersion.  If  the  distribution  sharply
+        differs from normal, the test will work incorrectly.
+
+        Input parameters:
+            X       -   sample. Array whose index goes from 0 to N-1.
+            N       -   size of sample.
+            Mean    -   assumed value of the mean.
+
+        Output parameters:
+            BothTails   -   p-value for two-tailed test.
+                            If BothTails is less than the given significance level
+                            the null hypothesis is rejected.
+            LeftTail    -   p-value for left-tailed test.
+                            If LeftTail is less than the given significance level,
+                            the null hypothesis is rejected.
+            RightTail   -   p-value for right-tailed test.
+                            If RightTail is less than the given significance level
+                            the null hypothesis is rejected.
+
+          -- ALGLIB --
+             Copyright 08.09.2006 by Bochkanov Sergey
+        *************************************************************************/
+        public static void studentttest1(double[] x,
+            int n,
+            double mean,
+            ref double bothtails,
+            ref double lefttail,
+            ref double righttail)
+        {
+            int i = 0;
+            double xmean = 0;
+            double xvariance = 0;
+            double xstddev = 0;
+            double v1 = 0;
+            double v2 = 0;
+            double stat = 0;
+            double s = 0;
+
+            bothtails = 0;
+            lefttail = 0;
+            righttail = 0;
+
+            if( n<=1 )
+            {
+                bothtails = 1.0;
+                lefttail = 1.0;
+                righttail = 1.0;
+                return;
+            }
+            
+            //
+            // Mean
+            //
+            xmean = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                xmean = xmean+x[i];
+            }
+            xmean = xmean/n;
+            
+            //
+            // Variance (using corrected two-pass algorithm)
+            //
+            xvariance = 0;
+            xstddev = 0;
+            if( n!=1 )
+            {
+                v1 = 0;
+                for(i=0; i<=n-1; i++)
+                {
+                    v1 = v1+math.sqr(x[i]-xmean);
+                }
+                v2 = 0;
+                for(i=0; i<=n-1; i++)
+                {
+                    v2 = v2+(x[i]-xmean);
+                }
+                v2 = math.sqr(v2)/n;
+                xvariance = (v1-v2)/(n-1);
+                if( (double)(xvariance)<(double)(0) )
+                {
+                    xvariance = 0;
+                }
+                xstddev = Math.Sqrt(xvariance);
+            }
+            if( (double)(xstddev)==(double)(0) )
+            {
+                bothtails = 1.0;
+                lefttail = 1.0;
+                righttail = 1.0;
+                return;
+            }
+            
+            //
+            // Statistic
+            //
+            stat = (xmean-mean)/(xstddev/Math.Sqrt(n));
+            s = studenttdistr.studenttdistribution(n-1, stat);
+            bothtails = 2*Math.Min(s, 1-s);
+            lefttail = s;
+            righttail = 1-s;
+        }
+
+
+        /*************************************************************************
+        Two-sample pooled test
+
+        This test checks three hypotheses about the mean of the given samples. The
+        following tests are performed:
+            * two-tailed test (null hypothesis - the means are equal)
+            * left-tailed test (null hypothesis - the mean of the first sample  is
+              greater than or equal to the mean of the second sample)
+            * right-tailed test (null hypothesis - the mean of the first sample is
+              less than or equal to the mean of the second sample).
+
+        Test is based on the following assumptions:
+            * given samples have normal distributions
+            * dispersions are equal
+            * samples are independent.
+
+        Input parameters:
+            X       -   sample 1. Array whose index goes from 0 to N-1.
+            N       -   size of sample.
+            Y       -   sample 2. Array whose index goes from 0 to M-1.
+            M       -   size of sample.
+
+        Output parameters:
+            BothTails   -   p-value for two-tailed test.
+                            If BothTails is less than the given significance level
+                            the null hypothesis is rejected.
+            LeftTail    -   p-value for left-tailed test.
+                            If LeftTail is less than the given significance level,
+                            the null hypothesis is rejected.
+            RightTail   -   p-value for right-tailed test.
+                            If RightTail is less than the given significance level
+                            the null hypothesis is rejected.
+
+          -- ALGLIB --
+             Copyright 18.09.2006 by Bochkanov Sergey
+        *************************************************************************/
+        public static void studentttest2(double[] x,
+            int n,
+            double[] y,
+            int m,
+            ref double bothtails,
+            ref double lefttail,
+            ref double righttail)
+        {
+            int i = 0;
+            double xmean = 0;
+            double ymean = 0;
+            double stat = 0;
+            double s = 0;
+            double p = 0;
+
+            bothtails = 0;
+            lefttail = 0;
+            righttail = 0;
+
+            if( n<=1 || m<=1 )
+            {
+                bothtails = 1.0;
+                lefttail = 1.0;
+                righttail = 1.0;
+                return;
+            }
+            
+            //
+            // Mean
+            //
+            xmean = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                xmean = xmean+x[i];
+            }
+            xmean = xmean/n;
+            ymean = 0;
+            for(i=0; i<=m-1; i++)
+            {
+                ymean = ymean+y[i];
+            }
+            ymean = ymean/m;
+            
+            //
+            // S
+            //
+            s = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                s = s+math.sqr(x[i]-xmean);
+            }
+            for(i=0; i<=m-1; i++)
+            {
+                s = s+math.sqr(y[i]-ymean);
+            }
+            s = Math.Sqrt(s*((double)1/(double)n+(double)1/(double)m)/(n+m-2));
+            if( (double)(s)==(double)(0) )
+            {
+                bothtails = 1.0;
+                lefttail = 1.0;
+                righttail = 1.0;
+                return;
+            }
+            
+            //
+            // Statistic
+            //
+            stat = (xmean-ymean)/s;
+            p = studenttdistr.studenttdistribution(n+m-2, stat);
+            bothtails = 2*Math.Min(p, 1-p);
+            lefttail = p;
+            righttail = 1-p;
+        }
+
+
+        /*************************************************************************
+        Two-sample unpooled test
+
+        This test checks three hypotheses about the mean of the given samples. The
+        following tests are performed:
+            * two-tailed test (null hypothesis - the means are equal)
+            * left-tailed test (null hypothesis - the mean of the first sample  is
+              greater than or equal to the mean of the second sample)
+            * right-tailed test (null hypothesis - the mean of the first sample is
+              less than or equal to the mean of the second sample).
+
+        Test is based on the following assumptions:
+            * given samples have normal distributions
+            * samples are independent.
+        Dispersion equality is not required
+
+        Input parameters:
+            X - sample 1. Array whose index goes from 0 to N-1.
+            N - size of the sample.
+            Y - sample 2. Array whose index goes from 0 to M-1.
+            M - size of the sample.
+
+        Output parameters:
+            BothTails   -   p-value for two-tailed test.
+                            If BothTails is less than the given significance level
+                            the null hypothesis is rejected.
+            LeftTail    -   p-value for left-tailed test.
+                            If LeftTail is less than the given significance level,
+                            the null hypothesis is rejected.
+            RightTail   -   p-value for right-tailed test.
+                            If RightTail is less than the given significance level
+                            the null hypothesis is rejected.
+
+          -- ALGLIB --
+             Copyright 18.09.2006 by Bochkanov Sergey
+        *************************************************************************/
+        public static void unequalvariancettest(double[] x,
+            int n,
+            double[] y,
+            int m,
+            ref double bothtails,
+            ref double lefttail,
+            ref double righttail)
+        {
+            int i = 0;
+            double xmean = 0;
+            double ymean = 0;
+            double xvar = 0;
+            double yvar = 0;
+            double df = 0;
+            double p = 0;
+            double stat = 0;
+            double c = 0;
+
+            bothtails = 0;
+            lefttail = 0;
+            righttail = 0;
+
+            if( n<=1 || m<=1 )
+            {
+                bothtails = 1.0;
+                lefttail = 1.0;
+                righttail = 1.0;
+                return;
+            }
+            
+            //
+            // Mean
+            //
+            xmean = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                xmean = xmean+x[i];
+            }
+            xmean = xmean/n;
+            ymean = 0;
+            for(i=0; i<=m-1; i++)
+            {
+                ymean = ymean+y[i];
+            }
+            ymean = ymean/m;
+            
+            //
+            // Variance (using corrected two-pass algorithm)
+            //
+            xvar = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                xvar = xvar+math.sqr(x[i]-xmean);
+            }
+            xvar = xvar/(n-1);
+            yvar = 0;
+            for(i=0; i<=m-1; i++)
+            {
+                yvar = yvar+math.sqr(y[i]-ymean);
+            }
+            yvar = yvar/(m-1);
+            if( (double)(xvar)==(double)(0) || (double)(yvar)==(double)(0) )
+            {
+                bothtails = 1.0;
+                lefttail = 1.0;
+                righttail = 1.0;
+                return;
+            }
+            
+            //
+            // Statistic
+            //
+            stat = (xmean-ymean)/Math.Sqrt(xvar/n+yvar/m);
+            c = xvar/n/(xvar/n+yvar/m);
+            df = (n-1)*(m-1)/((m-1)*math.sqr(c)+(n-1)*math.sqr(1-c));
+            if( (double)(stat)>(double)(0) )
+            {
+                p = 1-0.5*ibetaf.incompletebeta(df/2, 0.5, df/(df+math.sqr(stat)));
+            }
+            else
+            {
+                p = 0.5*ibetaf.incompletebeta(df/2, 0.5, df/(df+math.sqr(stat)));
+            }
+            bothtails = 2*Math.Min(p, 1-p);
+            lefttail = p;
+            righttail = 1-p;
+        }
+
+
+    }
+    public class variancetests
+    {
+        /*************************************************************************
+        Two-sample F-test
+
+        This test checks three hypotheses about dispersions of the given  samples.
+        The following tests are performed:
+            * two-tailed test (null hypothesis - the dispersions are equal)
+            * left-tailed test (null hypothesis  -  the  dispersion  of  the first
+              sample is greater than or equal to  the  dispersion  of  the  second
+              sample).
+            * right-tailed test (null hypothesis - the  dispersion  of  the  first
+              sample is less than or equal to the dispersion of the second sample)
+
+        The test is based on the following assumptions:
+            * the given samples have normal distributions
+            * the samples are independent.
+
+        Input parameters:
+            X   -   sample 1. Array whose index goes from 0 to N-1.
+            N   -   sample size.
+            Y   -   sample 2. Array whose index goes from 0 to M-1.
+            M   -   sample size.
+
+        Output parameters:
+            BothTails   -   p-value for two-tailed test.
+                            If BothTails is less than the given significance level
+                            the null hypothesis is rejected.
+            LeftTail    -   p-value for left-tailed test.
+                            If LeftTail is less than the given significance level,
+                            the null hypothesis is rejected.
+            RightTail   -   p-value for right-tailed test.
+                            If RightTail is less than the given significance level
+                            the null hypothesis is rejected.
+
+          -- ALGLIB --
+             Copyright 19.09.2006 by Bochkanov Sergey
+        *************************************************************************/
+        public static void ftest(double[] x,
+            int n,
+            double[] y,
+            int m,
+            ref double bothtails,
+            ref double lefttail,
+            ref double righttail)
+        {
+            int i = 0;
+            double xmean = 0;
+            double ymean = 0;
+            double xvar = 0;
+            double yvar = 0;
+            int df1 = 0;
+            int df2 = 0;
+            double stat = 0;
+
+            bothtails = 0;
+            lefttail = 0;
+            righttail = 0;
+
+            if( n<=2 || m<=2 )
+            {
+                bothtails = 1.0;
+                lefttail = 1.0;
+                righttail = 1.0;
+                return;
+            }
+            
+            //
+            // Mean
+            //
+            xmean = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                xmean = xmean+x[i];
+            }
+            xmean = xmean/n;
+            ymean = 0;
+            for(i=0; i<=m-1; i++)
+            {
+                ymean = ymean+y[i];
+            }
+            ymean = ymean/m;
+            
+            //
+            // Variance (using corrected two-pass algorithm)
+            //
+            xvar = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                xvar = xvar+math.sqr(x[i]-xmean);
+            }
+            xvar = xvar/(n-1);
+            yvar = 0;
+            for(i=0; i<=m-1; i++)
+            {
+                yvar = yvar+math.sqr(y[i]-ymean);
+            }
+            yvar = yvar/(m-1);
+            if( (double)(xvar)==(double)(0) || (double)(yvar)==(double)(0) )
+            {
+                bothtails = 1.0;
+                lefttail = 1.0;
+                righttail = 1.0;
+                return;
+            }
+            
+            //
+            // Statistic
+            //
+            df1 = n-1;
+            df2 = m-1;
+            stat = Math.Min(xvar/yvar, yvar/xvar);
+            bothtails = 1-(fdistr.fdistribution(df1, df2, 1/stat)-fdistr.fdistribution(df1, df2, stat));
+            lefttail = fdistr.fdistribution(df1, df2, xvar/yvar);
+            righttail = 1-lefttail;
+        }
+
+
+        /*************************************************************************
+        One-sample chi-square test
+
+        This test checks three hypotheses about the dispersion of the given sample
+        The following tests are performed:
+            * two-tailed test (null hypothesis - the dispersion equals  the  given
+              number)
+            * left-tailed test (null hypothesis - the dispersion is  greater  than
+              or equal to the given number)
+            * right-tailed test (null hypothesis  -  dispersion is  less  than  or
+              equal to the given number).
+
+        Test is based on the following assumptions:
+            * the given sample has a normal distribution.
+
+        Input parameters:
+            X           -   sample 1. Array whose index goes from 0 to N-1.
+            N           -   size of the sample.
+            Variance    -   dispersion value to compare with.
+
+        Output parameters:
+            BothTails   -   p-value for two-tailed test.
+                            If BothTails is less than the given significance level
+                            the null hypothesis is rejected.
+            LeftTail    -   p-value for left-tailed test.
+                            If LeftTail is less than the given significance level,
+                            the null hypothesis is rejected.
+            RightTail   -   p-value for right-tailed test.
+                            If RightTail is less than the given significance level
+                            the null hypothesis is rejected.
+
+          -- ALGLIB --
+             Copyright 19.09.2006 by Bochkanov Sergey
+        *************************************************************************/
+        public static void onesamplevariancetest(double[] x,
+            int n,
+            double variance,
+            ref double bothtails,
+            ref double lefttail,
+            ref double righttail)
+        {
+            int i = 0;
+            double xmean = 0;
+            double xvar = 0;
+            double s = 0;
+            double stat = 0;
+
+            bothtails = 0;
+            lefttail = 0;
+            righttail = 0;
+
+            if( n<=1 )
+            {
+                bothtails = 1.0;
+                lefttail = 1.0;
+                righttail = 1.0;
+                return;
+            }
+            
+            //
+            // Mean
+            //
+            xmean = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                xmean = xmean+x[i];
+            }
+            xmean = xmean/n;
+            
+            //
+            // Variance
+            //
+            xvar = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                xvar = xvar+math.sqr(x[i]-xmean);
+            }
+            xvar = xvar/(n-1);
+            if( (double)(xvar)==(double)(0) )
+            {
+                bothtails = 1.0;
+                lefttail = 1.0;
+                righttail = 1.0;
+                return;
+            }
+            
+            //
+            // Statistic
+            //
+            stat = (n-1)*xvar/variance;
+            s = chisquaredistr.chisquaredistribution(n-1, stat);
+            bothtails = 2*Math.Min(s, 1-s);
+            lefttail = s;
+            righttail = 1-lefttail;
+        }
+
+
+    }
+    public class wsr
+    {
+        /*************************************************************************
+        Wilcoxon signed-rank test
+
+        This test checks three hypotheses about the median  of  the  given sample.
+        The following tests are performed:
+            * two-tailed test (null hypothesis - the median is equal to the  given
+              value)
+            * left-tailed test (null hypothesis - the median is  greater  than  or
+              equal to the given value)
+            * right-tailed test (null hypothesis  -  the  median  is  less than or
+              equal to the given value)
+
+        Requirements:
+            * the scale of measurement should be ordinal, interval or  ratio (i.e.
+              the test could not be applied to nominal variables).
+            * the distribution should be continuous and symmetric relative to  its
+              median.
+            * number of distinct values in the X array should be greater than 4
+
+        The test is non-parametric and doesn't require distribution X to be normal
+
+        Input parameters:
+            X       -   sample. Array whose index goes from 0 to N-1.
+            N       -   size of the sample.
+            Median  -   assumed median value.
+
+        Output parameters:
+            BothTails   -   p-value for two-tailed test.
+                            If BothTails is less than the given significance level
+                            the null hypothesis is rejected.
+            LeftTail    -   p-value for left-tailed test.
+                            If LeftTail is less than the given significance level,
+                            the null hypothesis is rejected.
+            RightTail   -   p-value for right-tailed test.
+                            If RightTail is less than the given significance level
+                            the null hypothesis is rejected.
+
+        To calculate p-values, special approximation is used. This method lets  us
+        calculate p-values with two decimal places in interval [0.0001, 1].
+
+        "Two decimal places" does not sound very impressive, but in  practice  the
+        relative error of less than 1% is enough to make a decision.
+
+        There is no approximation outside the [0.0001, 1] interval. Therefore,  if
+        the significance level outlies this interval, the test returns 0.0001.
+
+          -- ALGLIB --
+             Copyright 08.09.2006 by Bochkanov Sergey
+        *************************************************************************/
+        public static void wilcoxonsignedranktest(double[] x,
+            int n,
+            double e,
+            ref double bothtails,
+            ref double lefttail,
+            ref double righttail)
+        {
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int t = 0;
+            double tmp = 0;
+            int tmpi = 0;
+            int ns = 0;
+            double[] r = new double[0];
+            int[] c = new int[0];
+            double w = 0;
+            double p = 0;
+            double mp = 0;
+            double s = 0;
+            double sigma = 0;
+            double mu = 0;
+
+            x = (double[])x.Clone();
+            bothtails = 0;
+            lefttail = 0;
+            righttail = 0;
+
+            
+            //
+            // Prepare
+            //
+            if( n<5 )
+            {
+                bothtails = 1.0;
+                lefttail = 1.0;
+                righttail = 1.0;
+                return;
+            }
+            ns = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                if( (double)(x[i])==(double)(e) )
+                {
+                    continue;
+                }
+                x[ns] = x[i];
+                ns = ns+1;
+            }
+            if( ns<5 )
+            {
+                bothtails = 1.0;
+                lefttail = 1.0;
+                righttail = 1.0;
+                return;
+            }
+            r = new double[ns-1+1];
+            c = new int[ns-1+1];
+            for(i=0; i<=ns-1; i++)
+            {
+                r[i] = Math.Abs(x[i]-e);
+                c[i] = i;
+            }
+            
+            //
+            // sort {R, C}
+            //
+            if( ns!=1 )
+            {
+                i = 2;
+                do
+                {
+                    t = i;
+                    while( t!=1 )
+                    {
+                        k = t/2;
+                        if( (double)(r[k-1])>=(double)(r[t-1]) )
+                        {
+                            t = 1;
+                        }
+                        else
+                        {
+                            tmp = r[k-1];
+                            r[k-1] = r[t-1];
+                            r[t-1] = tmp;
+                            tmpi = c[k-1];
+                            c[k-1] = c[t-1];
+                            c[t-1] = tmpi;
+                            t = k;
+                        }
+                    }
+                    i = i+1;
+                }
+                while( i<=ns );
+                i = ns-1;
+                do
+                {
+                    tmp = r[i];
+                    r[i] = r[0];
+                    r[0] = tmp;
+                    tmpi = c[i];
+                    c[i] = c[0];
+                    c[0] = tmpi;
+                    t = 1;
+                    while( t!=0 )
+                    {
+                        k = 2*t;
+                        if( k>i )
+                        {
+                            t = 0;
+                        }
+                        else
+                        {
+                            if( k<i )
+                            {
+                                if( (double)(r[k])>(double)(r[k-1]) )
+                                {
+                                    k = k+1;
+                                }
+                            }
+                            if( (double)(r[t-1])>=(double)(r[k-1]) )
+                            {
+                                t = 0;
+                            }
+                            else
+                            {
+                                tmp = r[k-1];
+                                r[k-1] = r[t-1];
+                                r[t-1] = tmp;
+                                tmpi = c[k-1];
+                                c[k-1] = c[t-1];
+                                c[t-1] = tmpi;
+                                t = k;
+                            }
+                        }
+                    }
+                    i = i-1;
+                }
+                while( i>=1 );
+            }
+            
+            //
+            // compute tied ranks
+            //
+            i = 0;
+            while( i<=ns-1 )
+            {
+                j = i+1;
+                while( j<=ns-1 )
+                {
+                    if( (double)(r[j])!=(double)(r[i]) )
+                    {
+                        break;
+                    }
+                    j = j+1;
+                }
+                for(k=i; k<=j-1; k++)
+                {
+                    r[k] = 1+(double)(i+j-1)/(double)2;
+                }
+                i = j;
+            }
+            
+            //
+            // Compute W+
+            //
+            w = 0;
+            for(i=0; i<=ns-1; i++)
+            {
+                if( (double)(x[c[i]])>(double)(e) )
+                {
+                    w = w+r[i];
+                }
+            }
+            
+            //
+            // Result
+            //
+            mu = (double)(ns*(ns+1))/(double)4;
+            sigma = Math.Sqrt((double)(ns*(ns+1)*(2*ns+1))/(double)24);
+            s = (w-mu)/sigma;
+            if( (double)(s)<=(double)(0) )
+            {
+                p = Math.Exp(wsigma(-((w-mu)/sigma), ns));
+                mp = 1-Math.Exp(wsigma(-((w-1-mu)/sigma), ns));
+            }
+            else
+            {
+                mp = Math.Exp(wsigma((w-mu)/sigma, ns));
+                p = 1-Math.Exp(wsigma((w+1-mu)/sigma, ns));
+            }
+            bothtails = Math.Max(2*Math.Min(p, mp), 1.0E-4);
+            lefttail = Math.Max(p, 1.0E-4);
+            righttail = Math.Max(mp, 1.0E-4);
+        }
+
+
+        /*************************************************************************
+        Sequential Chebyshev interpolation.
+        *************************************************************************/
+        private static void wcheb(double x,
+            double c,
+            ref double tj,
+            ref double tj1,
+            ref double r)
+        {
+            double t = 0;
+
+            r = r+c*tj;
+            t = 2*x*tj1-tj;
+            tj = tj1;
+            tj1 = t;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 5)
+        *************************************************************************/
+        private static double w5(double s)
+        {
+            double result = 0;
+            int w = 0;
+            double r = 0;
+
+            r = 0;
+            w = (int)Math.Round(-(3.708099e+00*s)+7.500000e+00);
+            if( w>=7 )
+            {
+                r = -6.931e-01;
+            }
+            if( w==6 )
+            {
+                r = -9.008e-01;
+            }
+            if( w==5 )
+            {
+                r = -1.163e+00;
+            }
+            if( w==4 )
+            {
+                r = -1.520e+00;
+            }
+            if( w==3 )
+            {
+                r = -1.856e+00;
+            }
+            if( w==2 )
+            {
+                r = -2.367e+00;
+            }
+            if( w==1 )
+            {
+                r = -2.773e+00;
+            }
+            if( w<=0 )
+            {
+                r = -3.466e+00;
+            }
+            result = r;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 6)
+        *************************************************************************/
+        private static double w6(double s)
+        {
+            double result = 0;
+            int w = 0;
+            double r = 0;
+
+            r = 0;
+            w = (int)Math.Round(-(4.769696e+00*s)+1.050000e+01);
+            if( w>=10 )
+            {
+                r = -6.931e-01;
+            }
+            if( w==9 )
+            {
+                r = -8.630e-01;
+            }
+            if( w==8 )
+            {
+                r = -1.068e+00;
+            }
+            if( w==7 )
+            {
+                r = -1.269e+00;
+            }
+            if( w==6 )
+            {
+                r = -1.520e+00;
+            }
+            if( w==5 )
+            {
+                r = -1.856e+00;
+            }
+            if( w==4 )
+            {
+                r = -2.213e+00;
+            }
+            if( w==3 )
+            {
+                r = -2.549e+00;
+            }
+            if( w==2 )
+            {
+                r = -3.060e+00;
+            }
+            if( w==1 )
+            {
+                r = -3.466e+00;
+            }
+            if( w<=0 )
+            {
+                r = -4.159e+00;
+            }
+            result = r;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 7)
+        *************************************************************************/
+        private static double w7(double s)
+        {
+            double result = 0;
+            int w = 0;
+            double r = 0;
+
+            r = 0;
+            w = (int)Math.Round(-(5.916080e+00*s)+1.400000e+01);
+            if( w>=14 )
+            {
+                r = -6.325e-01;
+            }
+            if( w==13 )
+            {
+                r = -7.577e-01;
+            }
+            if( w==12 )
+            {
+                r = -9.008e-01;
+            }
+            if( w==11 )
+            {
+                r = -1.068e+00;
+            }
+            if( w==10 )
+            {
+                r = -1.241e+00;
+            }
+            if( w==9 )
+            {
+                r = -1.451e+00;
+            }
+            if( w==8 )
+            {
+                r = -1.674e+00;
+            }
+            if( w==7 )
+            {
+                r = -1.908e+00;
+            }
+            if( w==6 )
+            {
+                r = -2.213e+00;
+            }
+            if( w==5 )
+            {
+                r = -2.549e+00;
+            }
+            if( w==4 )
+            {
+                r = -2.906e+00;
+            }
+            if( w==3 )
+            {
+                r = -3.243e+00;
+            }
+            if( w==2 )
+            {
+                r = -3.753e+00;
+            }
+            if( w==1 )
+            {
+                r = -4.159e+00;
+            }
+            if( w<=0 )
+            {
+                r = -4.852e+00;
+            }
+            result = r;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 8)
+        *************************************************************************/
+        private static double w8(double s)
+        {
+            double result = 0;
+            int w = 0;
+            double r = 0;
+
+            r = 0;
+            w = (int)Math.Round(-(7.141428e+00*s)+1.800000e+01);
+            if( w>=18 )
+            {
+                r = -6.399e-01;
+            }
+            if( w==17 )
+            {
+                r = -7.494e-01;
+            }
+            if( w==16 )
+            {
+                r = -8.630e-01;
+            }
+            if( w==15 )
+            {
+                r = -9.913e-01;
+            }
+            if( w==14 )
+            {
+                r = -1.138e+00;
+            }
+            if( w==13 )
+            {
+                r = -1.297e+00;
+            }
+            if( w==12 )
+            {
+                r = -1.468e+00;
+            }
+            if( w==11 )
+            {
+                r = -1.653e+00;
+            }
+            if( w==10 )
+            {
+                r = -1.856e+00;
+            }
+            if( w==9 )
+            {
+                r = -2.079e+00;
+            }
+            if( w==8 )
+            {
+                r = -2.326e+00;
+            }
+            if( w==7 )
+            {
+                r = -2.601e+00;
+            }
+            if( w==6 )
+            {
+                r = -2.906e+00;
+            }
+            if( w==5 )
+            {
+                r = -3.243e+00;
+            }
+            if( w==4 )
+            {
+                r = -3.599e+00;
+            }
+            if( w==3 )
+            {
+                r = -3.936e+00;
+            }
+            if( w==2 )
+            {
+                r = -4.447e+00;
+            }
+            if( w==1 )
+            {
+                r = -4.852e+00;
+            }
+            if( w<=0 )
+            {
+                r = -5.545e+00;
+            }
+            result = r;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 9)
+        *************************************************************************/
+        private static double w9(double s)
+        {
+            double result = 0;
+            int w = 0;
+            double r = 0;
+
+            r = 0;
+            w = (int)Math.Round(-(8.440972e+00*s)+2.250000e+01);
+            if( w>=22 )
+            {
+                r = -6.931e-01;
+            }
+            if( w==21 )
+            {
+                r = -7.873e-01;
+            }
+            if( w==20 )
+            {
+                r = -8.912e-01;
+            }
+            if( w==19 )
+            {
+                r = -1.002e+00;
+            }
+            if( w==18 )
+            {
+                r = -1.120e+00;
+            }
+            if( w==17 )
+            {
+                r = -1.255e+00;
+            }
+            if( w==16 )
+            {
+                r = -1.394e+00;
+            }
+            if( w==15 )
+            {
+                r = -1.547e+00;
+            }
+            if( w==14 )
+            {
+                r = -1.717e+00;
+            }
+            if( w==13 )
+            {
+                r = -1.895e+00;
+            }
+            if( w==12 )
+            {
+                r = -2.079e+00;
+            }
+            if( w==11 )
+            {
+                r = -2.287e+00;
+            }
+            if( w==10 )
+            {
+                r = -2.501e+00;
+            }
+            if( w==9 )
+            {
+                r = -2.742e+00;
+            }
+            if( w==8 )
+            {
+                r = -3.019e+00;
+            }
+            if( w==7 )
+            {
+                r = -3.294e+00;
+            }
+            if( w==6 )
+            {
+                r = -3.599e+00;
+            }
+            if( w==5 )
+            {
+                r = -3.936e+00;
+            }
+            if( w==4 )
+            {
+                r = -4.292e+00;
+            }
+            if( w==3 )
+            {
+                r = -4.629e+00;
+            }
+            if( w==2 )
+            {
+                r = -5.140e+00;
+            }
+            if( w==1 )
+            {
+                r = -5.545e+00;
+            }
+            if( w<=0 )
+            {
+                r = -6.238e+00;
+            }
+            result = r;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 10)
+        *************************************************************************/
+        private static double w10(double s)
+        {
+            double result = 0;
+            int w = 0;
+            double r = 0;
+
+            r = 0;
+            w = (int)Math.Round(-(9.810708e+00*s)+2.750000e+01);
+            if( w>=27 )
+            {
+                r = -6.931e-01;
+            }
+            if( w==26 )
+            {
+                r = -7.745e-01;
+            }
+            if( w==25 )
+            {
+                r = -8.607e-01;
+            }
+            if( w==24 )
+            {
+                r = -9.551e-01;
+            }
+            if( w==23 )
+            {
+                r = -1.057e+00;
+            }
+            if( w==22 )
+            {
+                r = -1.163e+00;
+            }
+            if( w==21 )
+            {
+                r = -1.279e+00;
+            }
+            if( w==20 )
+            {
+                r = -1.402e+00;
+            }
+            if( w==19 )
+            {
+                r = -1.533e+00;
+            }
+            if( w==18 )
+            {
+                r = -1.674e+00;
+            }
+            if( w==17 )
+            {
+                r = -1.826e+00;
+            }
+            if( w==16 )
+            {
+                r = -1.983e+00;
+            }
+            if( w==15 )
+            {
+                r = -2.152e+00;
+            }
+            if( w==14 )
+            {
+                r = -2.336e+00;
+            }
+            if( w==13 )
+            {
+                r = -2.525e+00;
+            }
+            if( w==12 )
+            {
+                r = -2.727e+00;
+            }
+            if( w==11 )
+            {
+                r = -2.942e+00;
+            }
+            if( w==10 )
+            {
+                r = -3.170e+00;
+            }
+            if( w==9 )
+            {
+                r = -3.435e+00;
+            }
+            if( w==8 )
+            {
+                r = -3.713e+00;
+            }
+            if( w==7 )
+            {
+                r = -3.987e+00;
+            }
+            if( w==6 )
+            {
+                r = -4.292e+00;
+            }
+            if( w==5 )
+            {
+                r = -4.629e+00;
+            }
+            if( w==4 )
+            {
+                r = -4.986e+00;
+            }
+            if( w==3 )
+            {
+                r = -5.322e+00;
+            }
+            if( w==2 )
+            {
+                r = -5.833e+00;
+            }
+            if( w==1 )
+            {
+                r = -6.238e+00;
+            }
+            if( w<=0 )
+            {
+                r = -6.931e+00;
+            }
+            result = r;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 11)
+        *************************************************************************/
+        private static double w11(double s)
+        {
+            double result = 0;
+            int w = 0;
+            double r = 0;
+
+            r = 0;
+            w = (int)Math.Round(-(1.124722e+01*s)+3.300000e+01);
+            if( w>=33 )
+            {
+                r = -6.595e-01;
+            }
+            if( w==32 )
+            {
+                r = -7.279e-01;
+            }
+            if( w==31 )
+            {
+                r = -8.002e-01;
+            }
+            if( w==30 )
+            {
+                r = -8.782e-01;
+            }
+            if( w==29 )
+            {
+                r = -9.615e-01;
+            }
+            if( w==28 )
+            {
+                r = -1.050e+00;
+            }
+            if( w==27 )
+            {
+                r = -1.143e+00;
+            }
+            if( w==26 )
+            {
+                r = -1.243e+00;
+            }
+            if( w==25 )
+            {
+                r = -1.348e+00;
+            }
+            if( w==24 )
+            {
+                r = -1.459e+00;
+            }
+            if( w==23 )
+            {
+                r = -1.577e+00;
+            }
+            if( w==22 )
+            {
+                r = -1.700e+00;
+            }
+            if( w==21 )
+            {
+                r = -1.832e+00;
+            }
+            if( w==20 )
+            {
+                r = -1.972e+00;
+            }
+            if( w==19 )
+            {
+                r = -2.119e+00;
+            }
+            if( w==18 )
+            {
+                r = -2.273e+00;
+            }
+            if( w==17 )
+            {
+                r = -2.437e+00;
+            }
+            if( w==16 )
+            {
+                r = -2.607e+00;
+            }
+            if( w==15 )
+            {
+                r = -2.788e+00;
+            }
+            if( w==14 )
+            {
+                r = -2.980e+00;
+            }
+            if( w==13 )
+            {
+                r = -3.182e+00;
+            }
+            if( w==12 )
+            {
+                r = -3.391e+00;
+            }
+            if( w==11 )
+            {
+                r = -3.617e+00;
+            }
+            if( w==10 )
+            {
+                r = -3.863e+00;
+            }
+            if( w==9 )
+            {
+                r = -4.128e+00;
+            }
+            if( w==8 )
+            {
+                r = -4.406e+00;
+            }
+            if( w==7 )
+            {
+                r = -4.680e+00;
+            }
+            if( w==6 )
+            {
+                r = -4.986e+00;
+            }
+            if( w==5 )
+            {
+                r = -5.322e+00;
+            }
+            if( w==4 )
+            {
+                r = -5.679e+00;
+            }
+            if( w==3 )
+            {
+                r = -6.015e+00;
+            }
+            if( w==2 )
+            {
+                r = -6.526e+00;
+            }
+            if( w==1 )
+            {
+                r = -6.931e+00;
+            }
+            if( w<=0 )
+            {
+                r = -7.625e+00;
+            }
+            result = r;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 12)
+        *************************************************************************/
+        private static double w12(double s)
+        {
+            double result = 0;
+            int w = 0;
+            double r = 0;
+
+            r = 0;
+            w = (int)Math.Round(-(1.274755e+01*s)+3.900000e+01);
+            if( w>=39 )
+            {
+                r = -6.633e-01;
+            }
+            if( w==38 )
+            {
+                r = -7.239e-01;
+            }
+            if( w==37 )
+            {
+                r = -7.878e-01;
+            }
+            if( w==36 )
+            {
+                r = -8.556e-01;
+            }
+            if( w==35 )
+            {
+                r = -9.276e-01;
+            }
+            if( w==34 )
+            {
+                r = -1.003e+00;
+            }
+            if( w==33 )
+            {
+                r = -1.083e+00;
+            }
+            if( w==32 )
+            {
+                r = -1.168e+00;
+            }
+            if( w==31 )
+            {
+                r = -1.256e+00;
+            }
+            if( w==30 )
+            {
+                r = -1.350e+00;
+            }
+            if( w==29 )
+            {
+                r = -1.449e+00;
+            }
+            if( w==28 )
+            {
+                r = -1.552e+00;
+            }
+            if( w==27 )
+            {
+                r = -1.660e+00;
+            }
+            if( w==26 )
+            {
+                r = -1.774e+00;
+            }
+            if( w==25 )
+            {
+                r = -1.893e+00;
+            }
+            if( w==24 )
+            {
+                r = -2.017e+00;
+            }
+            if( w==23 )
+            {
+                r = -2.148e+00;
+            }
+            if( w==22 )
+            {
+                r = -2.285e+00;
+            }
+            if( w==21 )
+            {
+                r = -2.429e+00;
+            }
+            if( w==20 )
+            {
+                r = -2.581e+00;
+            }
+            if( w==19 )
+            {
+                r = -2.738e+00;
+            }
+            if( w==18 )
+            {
+                r = -2.902e+00;
+            }
+            if( w==17 )
+            {
+                r = -3.076e+00;
+            }
+            if( w==16 )
+            {
+                r = -3.255e+00;
+            }
+            if( w==15 )
+            {
+                r = -3.443e+00;
+            }
+            if( w==14 )
+            {
+                r = -3.645e+00;
+            }
+            if( w==13 )
+            {
+                r = -3.852e+00;
+            }
+            if( w==12 )
+            {
+                r = -4.069e+00;
+            }
+            if( w==11 )
+            {
+                r = -4.310e+00;
+            }
+            if( w==10 )
+            {
+                r = -4.557e+00;
+            }
+            if( w==9 )
+            {
+                r = -4.821e+00;
+            }
+            if( w==8 )
+            {
+                r = -5.099e+00;
+            }
+            if( w==7 )
+            {
+                r = -5.373e+00;
+            }
+            if( w==6 )
+            {
+                r = -5.679e+00;
+            }
+            if( w==5 )
+            {
+                r = -6.015e+00;
+            }
+            if( w==4 )
+            {
+                r = -6.372e+00;
+            }
+            if( w==3 )
+            {
+                r = -6.708e+00;
+            }
+            if( w==2 )
+            {
+                r = -7.219e+00;
+            }
+            if( w==1 )
+            {
+                r = -7.625e+00;
+            }
+            if( w<=0 )
+            {
+                r = -8.318e+00;
+            }
+            result = r;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 13)
+        *************************************************************************/
+        private static double w13(double s)
+        {
+            double result = 0;
+            int w = 0;
+            double r = 0;
+
+            r = 0;
+            w = (int)Math.Round(-(1.430909e+01*s)+4.550000e+01);
+            if( w>=45 )
+            {
+                r = -6.931e-01;
+            }
+            if( w==44 )
+            {
+                r = -7.486e-01;
+            }
+            if( w==43 )
+            {
+                r = -8.068e-01;
+            }
+            if( w==42 )
+            {
+                r = -8.683e-01;
+            }
+            if( w==41 )
+            {
+                r = -9.328e-01;
+            }
+            if( w==40 )
+            {
+                r = -1.001e+00;
+            }
+            if( w==39 )
+            {
+                r = -1.072e+00;
+            }
+            if( w==38 )
+            {
+                r = -1.146e+00;
+            }
+            if( w==37 )
+            {
+                r = -1.224e+00;
+            }
+            if( w==36 )
+            {
+                r = -1.306e+00;
+            }
+            if( w==35 )
+            {
+                r = -1.392e+00;
+            }
+            if( w==34 )
+            {
+                r = -1.481e+00;
+            }
+            if( w==33 )
+            {
+                r = -1.574e+00;
+            }
+            if( w==32 )
+            {
+                r = -1.672e+00;
+            }
+            if( w==31 )
+            {
+                r = -1.773e+00;
+            }
+            if( w==30 )
+            {
+                r = -1.879e+00;
+            }
+            if( w==29 )
+            {
+                r = -1.990e+00;
+            }
+            if( w==28 )
+            {
+                r = -2.104e+00;
+            }
+            if( w==27 )
+            {
+                r = -2.224e+00;
+            }
+            if( w==26 )
+            {
+                r = -2.349e+00;
+            }
+            if( w==25 )
+            {
+                r = -2.479e+00;
+            }
+            if( w==24 )
+            {
+                r = -2.614e+00;
+            }
+            if( w==23 )
+            {
+                r = -2.755e+00;
+            }
+            if( w==22 )
+            {
+                r = -2.902e+00;
+            }
+            if( w==21 )
+            {
+                r = -3.055e+00;
+            }
+            if( w==20 )
+            {
+                r = -3.215e+00;
+            }
+            if( w==19 )
+            {
+                r = -3.380e+00;
+            }
+            if( w==18 )
+            {
+                r = -3.551e+00;
+            }
+            if( w==17 )
+            {
+                r = -3.733e+00;
+            }
+            if( w==16 )
+            {
+                r = -3.917e+00;
+            }
+            if( w==15 )
+            {
+                r = -4.113e+00;
+            }
+            if( w==14 )
+            {
+                r = -4.320e+00;
+            }
+            if( w==13 )
+            {
+                r = -4.534e+00;
+            }
+            if( w==12 )
+            {
+                r = -4.762e+00;
+            }
+            if( w==11 )
+            {
+                r = -5.004e+00;
+            }
+            if( w==10 )
+            {
+                r = -5.250e+00;
+            }
+            if( w==9 )
+            {
+                r = -5.514e+00;
+            }
+            if( w==8 )
+            {
+                r = -5.792e+00;
+            }
+            if( w==7 )
+            {
+                r = -6.066e+00;
+            }
+            if( w==6 )
+            {
+                r = -6.372e+00;
+            }
+            if( w==5 )
+            {
+                r = -6.708e+00;
+            }
+            if( w==4 )
+            {
+                r = -7.065e+00;
+            }
+            if( w==3 )
+            {
+                r = -7.401e+00;
+            }
+            if( w==2 )
+            {
+                r = -7.912e+00;
+            }
+            if( w==1 )
+            {
+                r = -8.318e+00;
+            }
+            if( w<=0 )
+            {
+                r = -9.011e+00;
+            }
+            result = r;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 14)
+        *************************************************************************/
+        private static double w14(double s)
+        {
+            double result = 0;
+            int w = 0;
+            double r = 0;
+
+            r = 0;
+            w = (int)Math.Round(-(1.592953e+01*s)+5.250000e+01);
+            if( w>=52 )
+            {
+                r = -6.931e-01;
+            }
+            if( w==51 )
+            {
+                r = -7.428e-01;
+            }
+            if( w==50 )
+            {
+                r = -7.950e-01;
+            }
+            if( w==49 )
+            {
+                r = -8.495e-01;
+            }
+            if( w==48 )
+            {
+                r = -9.067e-01;
+            }
+            if( w==47 )
+            {
+                r = -9.664e-01;
+            }
+            if( w==46 )
+            {
+                r = -1.029e+00;
+            }
+            if( w==45 )
+            {
+                r = -1.094e+00;
+            }
+            if( w==44 )
+            {
+                r = -1.162e+00;
+            }
+            if( w==43 )
+            {
+                r = -1.233e+00;
+            }
+            if( w==42 )
+            {
+                r = -1.306e+00;
+            }
+            if( w==41 )
+            {
+                r = -1.383e+00;
+            }
+            if( w==40 )
+            {
+                r = -1.463e+00;
+            }
+            if( w==39 )
+            {
+                r = -1.546e+00;
+            }
+            if( w==38 )
+            {
+                r = -1.632e+00;
+            }
+            if( w==37 )
+            {
+                r = -1.722e+00;
+            }
+            if( w==36 )
+            {
+                r = -1.815e+00;
+            }
+            if( w==35 )
+            {
+                r = -1.911e+00;
+            }
+            if( w==34 )
+            {
+                r = -2.011e+00;
+            }
+            if( w==33 )
+            {
+                r = -2.115e+00;
+            }
+            if( w==32 )
+            {
+                r = -2.223e+00;
+            }
+            if( w==31 )
+            {
+                r = -2.334e+00;
+            }
+            if( w==30 )
+            {
+                r = -2.450e+00;
+            }
+            if( w==29 )
+            {
+                r = -2.570e+00;
+            }
+            if( w==28 )
+            {
+                r = -2.694e+00;
+            }
+            if( w==27 )
+            {
+                r = -2.823e+00;
+            }
+            if( w==26 )
+            {
+                r = -2.956e+00;
+            }
+            if( w==25 )
+            {
+                r = -3.095e+00;
+            }
+            if( w==24 )
+            {
+                r = -3.238e+00;
+            }
+            if( w==23 )
+            {
+                r = -3.387e+00;
+            }
+            if( w==22 )
+            {
+                r = -3.541e+00;
+            }
+            if( w==21 )
+            {
+                r = -3.700e+00;
+            }
+            if( w==20 )
+            {
+                r = -3.866e+00;
+            }
+            if( w==19 )
+            {
+                r = -4.038e+00;
+            }
+            if( w==18 )
+            {
+                r = -4.215e+00;
+            }
+            if( w==17 )
+            {
+                r = -4.401e+00;
+            }
+            if( w==16 )
+            {
+                r = -4.592e+00;
+            }
+            if( w==15 )
+            {
+                r = -4.791e+00;
+            }
+            if( w==14 )
+            {
+                r = -5.004e+00;
+            }
+            if( w==13 )
+            {
+                r = -5.227e+00;
+            }
+            if( w==12 )
+            {
+                r = -5.456e+00;
+            }
+            if( w==11 )
+            {
+                r = -5.697e+00;
+            }
+            if( w==10 )
+            {
+                r = -5.943e+00;
+            }
+            if( w==9 )
+            {
+                r = -6.208e+00;
+            }
+            if( w==8 )
+            {
+                r = -6.485e+00;
+            }
+            if( w==7 )
+            {
+                r = -6.760e+00;
+            }
+            if( w==6 )
+            {
+                r = -7.065e+00;
+            }
+            if( w==5 )
+            {
+                r = -7.401e+00;
+            }
+            if( w==4 )
+            {
+                r = -7.758e+00;
+            }
+            if( w==3 )
+            {
+                r = -8.095e+00;
+            }
+            if( w==2 )
+            {
+                r = -8.605e+00;
+            }
+            if( w==1 )
+            {
+                r = -9.011e+00;
+            }
+            if( w<=0 )
+            {
+                r = -9.704e+00;
+            }
+            result = r;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 15)
+        *************************************************************************/
+        private static double w15(double s)
+        {
+            double result = 0;
+            int w = 0;
+            double r = 0;
+
+            r = 0;
+            w = (int)Math.Round(-(1.760682e+01*s)+6.000000e+01);
+            if( w>=60 )
+            {
+                r = -6.714e-01;
+            }
+            if( w==59 )
+            {
+                r = -7.154e-01;
+            }
+            if( w==58 )
+            {
+                r = -7.613e-01;
+            }
+            if( w==57 )
+            {
+                r = -8.093e-01;
+            }
+            if( w==56 )
+            {
+                r = -8.593e-01;
+            }
+            if( w==55 )
+            {
+                r = -9.114e-01;
+            }
+            if( w==54 )
+            {
+                r = -9.656e-01;
+            }
+            if( w==53 )
+            {
+                r = -1.022e+00;
+            }
+            if( w==52 )
+            {
+                r = -1.081e+00;
+            }
+            if( w==51 )
+            {
+                r = -1.142e+00;
+            }
+            if( w==50 )
+            {
+                r = -1.205e+00;
+            }
+            if( w==49 )
+            {
+                r = -1.270e+00;
+            }
+            if( w==48 )
+            {
+                r = -1.339e+00;
+            }
+            if( w==47 )
+            {
+                r = -1.409e+00;
+            }
+            if( w==46 )
+            {
+                r = -1.482e+00;
+            }
+            if( w==45 )
+            {
+                r = -1.558e+00;
+            }
+            if( w==44 )
+            {
+                r = -1.636e+00;
+            }
+            if( w==43 )
+            {
+                r = -1.717e+00;
+            }
+            if( w==42 )
+            {
+                r = -1.801e+00;
+            }
+            if( w==41 )
+            {
+                r = -1.888e+00;
+            }
+            if( w==40 )
+            {
+                r = -1.977e+00;
+            }
+            if( w==39 )
+            {
+                r = -2.070e+00;
+            }
+            if( w==38 )
+            {
+                r = -2.166e+00;
+            }
+            if( w==37 )
+            {
+                r = -2.265e+00;
+            }
+            if( w==36 )
+            {
+                r = -2.366e+00;
+            }
+            if( w==35 )
+            {
+                r = -2.472e+00;
+            }
+            if( w==34 )
+            {
+                r = -2.581e+00;
+            }
+            if( w==33 )
+            {
+                r = -2.693e+00;
+            }
+            if( w==32 )
+            {
+                r = -2.809e+00;
+            }
+            if( w==31 )
+            {
+                r = -2.928e+00;
+            }
+            if( w==30 )
+            {
+                r = -3.051e+00;
+            }
+            if( w==29 )
+            {
+                r = -3.179e+00;
+            }
+            if( w==28 )
+            {
+                r = -3.310e+00;
+            }
+            if( w==27 )
+            {
+                r = -3.446e+00;
+            }
+            if( w==26 )
+            {
+                r = -3.587e+00;
+            }
+            if( w==25 )
+            {
+                r = -3.732e+00;
+            }
+            if( w==24 )
+            {
+                r = -3.881e+00;
+            }
+            if( w==23 )
+            {
+                r = -4.036e+00;
+            }
+            if( w==22 )
+            {
+                r = -4.195e+00;
+            }
+            if( w==21 )
+            {
+                r = -4.359e+00;
+            }
+            if( w==20 )
+            {
+                r = -4.531e+00;
+            }
+            if( w==19 )
+            {
+                r = -4.707e+00;
+            }
+            if( w==18 )
+            {
+                r = -4.888e+00;
+            }
+            if( w==17 )
+            {
+                r = -5.079e+00;
+            }
+            if( w==16 )
+            {
+                r = -5.273e+00;
+            }
+            if( w==15 )
+            {
+                r = -5.477e+00;
+            }
+            if( w==14 )
+            {
+                r = -5.697e+00;
+            }
+            if( w==13 )
+            {
+                r = -5.920e+00;
+            }
+            if( w==12 )
+            {
+                r = -6.149e+00;
+            }
+            if( w==11 )
+            {
+                r = -6.390e+00;
+            }
+            if( w==10 )
+            {
+                r = -6.636e+00;
+            }
+            if( w==9 )
+            {
+                r = -6.901e+00;
+            }
+            if( w==8 )
+            {
+                r = -7.178e+00;
+            }
+            if( w==7 )
+            {
+                r = -7.453e+00;
+            }
+            if( w==6 )
+            {
+                r = -7.758e+00;
+            }
+            if( w==5 )
+            {
+                r = -8.095e+00;
+            }
+            if( w==4 )
+            {
+                r = -8.451e+00;
+            }
+            if( w==3 )
+            {
+                r = -8.788e+00;
+            }
+            if( w==2 )
+            {
+                r = -9.299e+00;
+            }
+            if( w==1 )
+            {
+                r = -9.704e+00;
+            }
+            if( w<=0 )
+            {
+                r = -1.040e+01;
+            }
+            result = r;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 16)
+        *************************************************************************/
+        private static double w16(double s)
+        {
+            double result = 0;
+            int w = 0;
+            double r = 0;
+
+            r = 0;
+            w = (int)Math.Round(-(1.933908e+01*s)+6.800000e+01);
+            if( w>=68 )
+            {
+                r = -6.733e-01;
+            }
+            if( w==67 )
+            {
+                r = -7.134e-01;
+            }
+            if( w==66 )
+            {
+                r = -7.551e-01;
+            }
+            if( w==65 )
+            {
+                r = -7.986e-01;
+            }
+            if( w==64 )
+            {
+                r = -8.437e-01;
+            }
+            if( w==63 )
+            {
+                r = -8.905e-01;
+            }
+            if( w==62 )
+            {
+                r = -9.391e-01;
+            }
+            if( w==61 )
+            {
+                r = -9.895e-01;
+            }
+            if( w==60 )
+            {
+                r = -1.042e+00;
+            }
+            if( w==59 )
+            {
+                r = -1.096e+00;
+            }
+            if( w==58 )
+            {
+                r = -1.152e+00;
+            }
+            if( w==57 )
+            {
+                r = -1.210e+00;
+            }
+            if( w==56 )
+            {
+                r = -1.270e+00;
+            }
+            if( w==55 )
+            {
+                r = -1.331e+00;
+            }
+            if( w==54 )
+            {
+                r = -1.395e+00;
+            }
+            if( w==53 )
+            {
+                r = -1.462e+00;
+            }
+            if( w==52 )
+            {
+                r = -1.530e+00;
+            }
+            if( w==51 )
+            {
+                r = -1.600e+00;
+            }
+            if( w==50 )
+            {
+                r = -1.673e+00;
+            }
+            if( w==49 )
+            {
+                r = -1.748e+00;
+            }
+            if( w==48 )
+            {
+                r = -1.825e+00;
+            }
+            if( w==47 )
+            {
+                r = -1.904e+00;
+            }
+            if( w==46 )
+            {
+                r = -1.986e+00;
+            }
+            if( w==45 )
+            {
+                r = -2.071e+00;
+            }
+            if( w==44 )
+            {
+                r = -2.158e+00;
+            }
+            if( w==43 )
+            {
+                r = -2.247e+00;
+            }
+            if( w==42 )
+            {
+                r = -2.339e+00;
+            }
+            if( w==41 )
+            {
+                r = -2.434e+00;
+            }
+            if( w==40 )
+            {
+                r = -2.532e+00;
+            }
+            if( w==39 )
+            {
+                r = -2.632e+00;
+            }
+            if( w==38 )
+            {
+                r = -2.735e+00;
+            }
+            if( w==37 )
+            {
+                r = -2.842e+00;
+            }
+            if( w==36 )
+            {
+                r = -2.951e+00;
+            }
+            if( w==35 )
+            {
+                r = -3.064e+00;
+            }
+            if( w==34 )
+            {
+                r = -3.179e+00;
+            }
+            if( w==33 )
+            {
+                r = -3.298e+00;
+            }
+            if( w==32 )
+            {
+                r = -3.420e+00;
+            }
+            if( w==31 )
+            {
+                r = -3.546e+00;
+            }
+            if( w==30 )
+            {
+                r = -3.676e+00;
+            }
+            if( w==29 )
+            {
+                r = -3.810e+00;
+            }
+            if( w==28 )
+            {
+                r = -3.947e+00;
+            }
+            if( w==27 )
+            {
+                r = -4.088e+00;
+            }
+            if( w==26 )
+            {
+                r = -4.234e+00;
+            }
+            if( w==25 )
+            {
+                r = -4.383e+00;
+            }
+            if( w==24 )
+            {
+                r = -4.538e+00;
+            }
+            if( w==23 )
+            {
+                r = -4.697e+00;
+            }
+            if( w==22 )
+            {
+                r = -4.860e+00;
+            }
+            if( w==21 )
+            {
+                r = -5.029e+00;
+            }
+            if( w==20 )
+            {
+                r = -5.204e+00;
+            }
+            if( w==19 )
+            {
+                r = -5.383e+00;
+            }
+            if( w==18 )
+            {
+                r = -5.569e+00;
+            }
+            if( w==17 )
+            {
+                r = -5.762e+00;
+            }
+            if( w==16 )
+            {
+                r = -5.960e+00;
+            }
+            if( w==15 )
+            {
+                r = -6.170e+00;
+            }
+            if( w==14 )
+            {
+                r = -6.390e+00;
+            }
+            if( w==13 )
+            {
+                r = -6.613e+00;
+            }
+            if( w==12 )
+            {
+                r = -6.842e+00;
+            }
+            if( w==11 )
+            {
+                r = -7.083e+00;
+            }
+            if( w==10 )
+            {
+                r = -7.329e+00;
+            }
+            if( w==9 )
+            {
+                r = -7.594e+00;
+            }
+            if( w==8 )
+            {
+                r = -7.871e+00;
+            }
+            if( w==7 )
+            {
+                r = -8.146e+00;
+            }
+            if( w==6 )
+            {
+                r = -8.451e+00;
+            }
+            if( w==5 )
+            {
+                r = -8.788e+00;
+            }
+            if( w==4 )
+            {
+                r = -9.144e+00;
+            }
+            if( w==3 )
+            {
+                r = -9.481e+00;
+            }
+            if( w==2 )
+            {
+                r = -9.992e+00;
+            }
+            if( w==1 )
+            {
+                r = -1.040e+01;
+            }
+            if( w<=0 )
+            {
+                r = -1.109e+01;
+            }
+            result = r;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 17)
+        *************************************************************************/
+        private static double w17(double s)
+        {
+            double result = 0;
+            int w = 0;
+            double r = 0;
+
+            r = 0;
+            w = (int)Math.Round(-(2.112463e+01*s)+7.650000e+01);
+            if( w>=76 )
+            {
+                r = -6.931e-01;
+            }
+            if( w==75 )
+            {
+                r = -7.306e-01;
+            }
+            if( w==74 )
+            {
+                r = -7.695e-01;
+            }
+            if( w==73 )
+            {
+                r = -8.097e-01;
+            }
+            if( w==72 )
+            {
+                r = -8.514e-01;
+            }
+            if( w==71 )
+            {
+                r = -8.946e-01;
+            }
+            if( w==70 )
+            {
+                r = -9.392e-01;
+            }
+            if( w==69 )
+            {
+                r = -9.853e-01;
+            }
+            if( w==68 )
+            {
+                r = -1.033e+00;
+            }
+            if( w==67 )
+            {
+                r = -1.082e+00;
+            }
+            if( w==66 )
+            {
+                r = -1.133e+00;
+            }
+            if( w==65 )
+            {
+                r = -1.185e+00;
+            }
+            if( w==64 )
+            {
+                r = -1.240e+00;
+            }
+            if( w==63 )
+            {
+                r = -1.295e+00;
+            }
+            if( w==62 )
+            {
+                r = -1.353e+00;
+            }
+            if( w==61 )
+            {
+                r = -1.412e+00;
+            }
+            if( w==60 )
+            {
+                r = -1.473e+00;
+            }
+            if( w==59 )
+            {
+                r = -1.536e+00;
+            }
+            if( w==58 )
+            {
+                r = -1.600e+00;
+            }
+            if( w==57 )
+            {
+                r = -1.666e+00;
+            }
+            if( w==56 )
+            {
+                r = -1.735e+00;
+            }
+            if( w==55 )
+            {
+                r = -1.805e+00;
+            }
+            if( w==54 )
+            {
+                r = -1.877e+00;
+            }
+            if( w==53 )
+            {
+                r = -1.951e+00;
+            }
+            if( w==52 )
+            {
+                r = -2.028e+00;
+            }
+            if( w==51 )
+            {
+                r = -2.106e+00;
+            }
+            if( w==50 )
+            {
+                r = -2.186e+00;
+            }
+            if( w==49 )
+            {
+                r = -2.269e+00;
+            }
+            if( w==48 )
+            {
+                r = -2.353e+00;
+            }
+            if( w==47 )
+            {
+                r = -2.440e+00;
+            }
+            if( w==46 )
+            {
+                r = -2.530e+00;
+            }
+            if( w==45 )
+            {
+                r = -2.621e+00;
+            }
+            if( w==44 )
+            {
+                r = -2.715e+00;
+            }
+            if( w==43 )
+            {
+                r = -2.812e+00;
+            }
+            if( w==42 )
+            {
+                r = -2.911e+00;
+            }
+            if( w==41 )
+            {
+                r = -3.012e+00;
+            }
+            if( w==40 )
+            {
+                r = -3.116e+00;
+            }
+            if( w==39 )
+            {
+                r = -3.223e+00;
+            }
+            if( w==38 )
+            {
+                r = -3.332e+00;
+            }
+            if( w==37 )
+            {
+                r = -3.445e+00;
+            }
+            if( w==36 )
+            {
+                r = -3.560e+00;
+            }
+            if( w==35 )
+            {
+                r = -3.678e+00;
+            }
+            if( w==34 )
+            {
+                r = -3.799e+00;
+            }
+            if( w==33 )
+            {
+                r = -3.924e+00;
+            }
+            if( w==32 )
+            {
+                r = -4.052e+00;
+            }
+            if( w==31 )
+            {
+                r = -4.183e+00;
+            }
+            if( w==30 )
+            {
+                r = -4.317e+00;
+            }
+            if( w==29 )
+            {
+                r = -4.456e+00;
+            }
+            if( w==28 )
+            {
+                r = -4.597e+00;
+            }
+            if( w==27 )
+            {
+                r = -4.743e+00;
+            }
+            if( w==26 )
+            {
+                r = -4.893e+00;
+            }
+            if( w==25 )
+            {
+                r = -5.047e+00;
+            }
+            if( w==24 )
+            {
+                r = -5.204e+00;
+            }
+            if( w==23 )
+            {
+                r = -5.367e+00;
+            }
+            if( w==22 )
+            {
+                r = -5.534e+00;
+            }
+            if( w==21 )
+            {
+                r = -5.706e+00;
+            }
+            if( w==20 )
+            {
+                r = -5.884e+00;
+            }
+            if( w==19 )
+            {
+                r = -6.066e+00;
+            }
+            if( w==18 )
+            {
+                r = -6.254e+00;
+            }
+            if( w==17 )
+            {
+                r = -6.451e+00;
+            }
+            if( w==16 )
+            {
+                r = -6.654e+00;
+            }
+            if( w==15 )
+            {
+                r = -6.864e+00;
+            }
+            if( w==14 )
+            {
+                r = -7.083e+00;
+            }
+            if( w==13 )
+            {
+                r = -7.306e+00;
+            }
+            if( w==12 )
+            {
+                r = -7.535e+00;
+            }
+            if( w==11 )
+            {
+                r = -7.776e+00;
+            }
+            if( w==10 )
+            {
+                r = -8.022e+00;
+            }
+            if( w==9 )
+            {
+                r = -8.287e+00;
+            }
+            if( w==8 )
+            {
+                r = -8.565e+00;
+            }
+            if( w==7 )
+            {
+                r = -8.839e+00;
+            }
+            if( w==6 )
+            {
+                r = -9.144e+00;
+            }
+            if( w==5 )
+            {
+                r = -9.481e+00;
+            }
+            if( w==4 )
+            {
+                r = -9.838e+00;
+            }
+            if( w==3 )
+            {
+                r = -1.017e+01;
+            }
+            if( w==2 )
+            {
+                r = -1.068e+01;
+            }
+            if( w==1 )
+            {
+                r = -1.109e+01;
+            }
+            if( w<=0 )
+            {
+                r = -1.178e+01;
+            }
+            result = r;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 18)
+        *************************************************************************/
+        private static double w18(double s)
+        {
+            double result = 0;
+            int w = 0;
+            double r = 0;
+
+            r = 0;
+            w = (int)Math.Round(-(2.296193e+01*s)+8.550000e+01);
+            if( w>=85 )
+            {
+                r = -6.931e-01;
+            }
+            if( w==84 )
+            {
+                r = -7.276e-01;
+            }
+            if( w==83 )
+            {
+                r = -7.633e-01;
+            }
+            if( w==82 )
+            {
+                r = -8.001e-01;
+            }
+            if( w==81 )
+            {
+                r = -8.381e-01;
+            }
+            if( w==80 )
+            {
+                r = -8.774e-01;
+            }
+            if( w==79 )
+            {
+                r = -9.179e-01;
+            }
+            if( w==78 )
+            {
+                r = -9.597e-01;
+            }
+            if( w==77 )
+            {
+                r = -1.003e+00;
+            }
+            if( w==76 )
+            {
+                r = -1.047e+00;
+            }
+            if( w==75 )
+            {
+                r = -1.093e+00;
+            }
+            if( w==74 )
+            {
+                r = -1.140e+00;
+            }
+            if( w==73 )
+            {
+                r = -1.188e+00;
+            }
+            if( w==72 )
+            {
+                r = -1.238e+00;
+            }
+            if( w==71 )
+            {
+                r = -1.289e+00;
+            }
+            if( w==70 )
+            {
+                r = -1.342e+00;
+            }
+            if( w==69 )
+            {
+                r = -1.396e+00;
+            }
+            if( w==68 )
+            {
+                r = -1.452e+00;
+            }
+            if( w==67 )
+            {
+                r = -1.509e+00;
+            }
+            if( w==66 )
+            {
+                r = -1.568e+00;
+            }
+            if( w==65 )
+            {
+                r = -1.628e+00;
+            }
+            if( w==64 )
+            {
+                r = -1.690e+00;
+            }
+            if( w==63 )
+            {
+                r = -1.753e+00;
+            }
+            if( w==62 )
+            {
+                r = -1.818e+00;
+            }
+            if( w==61 )
+            {
+                r = -1.885e+00;
+            }
+            if( w==60 )
+            {
+                r = -1.953e+00;
+            }
+            if( w==59 )
+            {
+                r = -2.023e+00;
+            }
+            if( w==58 )
+            {
+                r = -2.095e+00;
+            }
+            if( w==57 )
+            {
+                r = -2.168e+00;
+            }
+            if( w==56 )
+            {
+                r = -2.244e+00;
+            }
+            if( w==55 )
+            {
+                r = -2.321e+00;
+            }
+            if( w==54 )
+            {
+                r = -2.400e+00;
+            }
+            if( w==53 )
+            {
+                r = -2.481e+00;
+            }
+            if( w==52 )
+            {
+                r = -2.564e+00;
+            }
+            if( w==51 )
+            {
+                r = -2.648e+00;
+            }
+            if( w==50 )
+            {
+                r = -2.735e+00;
+            }
+            if( w==49 )
+            {
+                r = -2.824e+00;
+            }
+            if( w==48 )
+            {
+                r = -2.915e+00;
+            }
+            if( w==47 )
+            {
+                r = -3.008e+00;
+            }
+            if( w==46 )
+            {
+                r = -3.104e+00;
+            }
+            if( w==45 )
+            {
+                r = -3.201e+00;
+            }
+            if( w==44 )
+            {
+                r = -3.301e+00;
+            }
+            if( w==43 )
+            {
+                r = -3.403e+00;
+            }
+            if( w==42 )
+            {
+                r = -3.508e+00;
+            }
+            if( w==41 )
+            {
+                r = -3.615e+00;
+            }
+            if( w==40 )
+            {
+                r = -3.724e+00;
+            }
+            if( w==39 )
+            {
+                r = -3.836e+00;
+            }
+            if( w==38 )
+            {
+                r = -3.950e+00;
+            }
+            if( w==37 )
+            {
+                r = -4.068e+00;
+            }
+            if( w==36 )
+            {
+                r = -4.188e+00;
+            }
+            if( w==35 )
+            {
+                r = -4.311e+00;
+            }
+            if( w==34 )
+            {
+                r = -4.437e+00;
+            }
+            if( w==33 )
+            {
+                r = -4.565e+00;
+            }
+            if( w==32 )
+            {
+                r = -4.698e+00;
+            }
+            if( w==31 )
+            {
+                r = -4.833e+00;
+            }
+            if( w==30 )
+            {
+                r = -4.971e+00;
+            }
+            if( w==29 )
+            {
+                r = -5.113e+00;
+            }
+            if( w==28 )
+            {
+                r = -5.258e+00;
+            }
+            if( w==27 )
+            {
+                r = -5.408e+00;
+            }
+            if( w==26 )
+            {
+                r = -5.561e+00;
+            }
+            if( w==25 )
+            {
+                r = -5.717e+00;
+            }
+            if( w==24 )
+            {
+                r = -5.878e+00;
+            }
+            if( w==23 )
+            {
+                r = -6.044e+00;
+            }
+            if( w==22 )
+            {
+                r = -6.213e+00;
+            }
+            if( w==21 )
+            {
+                r = -6.388e+00;
+            }
+            if( w==20 )
+            {
+                r = -6.569e+00;
+            }
+            if( w==19 )
+            {
+                r = -6.753e+00;
+            }
+            if( w==18 )
+            {
+                r = -6.943e+00;
+            }
+            if( w==17 )
+            {
+                r = -7.144e+00;
+            }
+            if( w==16 )
+            {
+                r = -7.347e+00;
+            }
+            if( w==15 )
+            {
+                r = -7.557e+00;
+            }
+            if( w==14 )
+            {
+                r = -7.776e+00;
+            }
+            if( w==13 )
+            {
+                r = -7.999e+00;
+            }
+            if( w==12 )
+            {
+                r = -8.228e+00;
+            }
+            if( w==11 )
+            {
+                r = -8.469e+00;
+            }
+            if( w==10 )
+            {
+                r = -8.715e+00;
+            }
+            if( w==9 )
+            {
+                r = -8.980e+00;
+            }
+            if( w==8 )
+            {
+                r = -9.258e+00;
+            }
+            if( w==7 )
+            {
+                r = -9.532e+00;
+            }
+            if( w==6 )
+            {
+                r = -9.838e+00;
+            }
+            if( w==5 )
+            {
+                r = -1.017e+01;
+            }
+            if( w==4 )
+            {
+                r = -1.053e+01;
+            }
+            if( w==3 )
+            {
+                r = -1.087e+01;
+            }
+            if( w==2 )
+            {
+                r = -1.138e+01;
+            }
+            if( w==1 )
+            {
+                r = -1.178e+01;
+            }
+            if( w<=0 )
+            {
+                r = -1.248e+01;
+            }
+            result = r;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 19)
+        *************************************************************************/
+        private static double w19(double s)
+        {
+            double result = 0;
+            int w = 0;
+            double r = 0;
+
+            r = 0;
+            w = (int)Math.Round(-(2.484955e+01*s)+9.500000e+01);
+            if( w>=95 )
+            {
+                r = -6.776e-01;
+            }
+            if( w==94 )
+            {
+                r = -7.089e-01;
+            }
+            if( w==93 )
+            {
+                r = -7.413e-01;
+            }
+            if( w==92 )
+            {
+                r = -7.747e-01;
+            }
+            if( w==91 )
+            {
+                r = -8.090e-01;
+            }
+            if( w==90 )
+            {
+                r = -8.445e-01;
+            }
+            if( w==89 )
+            {
+                r = -8.809e-01;
+            }
+            if( w==88 )
+            {
+                r = -9.185e-01;
+            }
+            if( w==87 )
+            {
+                r = -9.571e-01;
+            }
+            if( w==86 )
+            {
+                r = -9.968e-01;
+            }
+            if( w==85 )
+            {
+                r = -1.038e+00;
+            }
+            if( w==84 )
+            {
+                r = -1.080e+00;
+            }
+            if( w==83 )
+            {
+                r = -1.123e+00;
+            }
+            if( w==82 )
+            {
+                r = -1.167e+00;
+            }
+            if( w==81 )
+            {
+                r = -1.213e+00;
+            }
+            if( w==80 )
+            {
+                r = -1.259e+00;
+            }
+            if( w==79 )
+            {
+                r = -1.307e+00;
+            }
+            if( w==78 )
+            {
+                r = -1.356e+00;
+            }
+            if( w==77 )
+            {
+                r = -1.407e+00;
+            }
+            if( w==76 )
+            {
+                r = -1.458e+00;
+            }
+            if( w==75 )
+            {
+                r = -1.511e+00;
+            }
+            if( w==74 )
+            {
+                r = -1.565e+00;
+            }
+            if( w==73 )
+            {
+                r = -1.621e+00;
+            }
+            if( w==72 )
+            {
+                r = -1.678e+00;
+            }
+            if( w==71 )
+            {
+                r = -1.736e+00;
+            }
+            if( w==70 )
+            {
+                r = -1.796e+00;
+            }
+            if( w==69 )
+            {
+                r = -1.857e+00;
+            }
+            if( w==68 )
+            {
+                r = -1.919e+00;
+            }
+            if( w==67 )
+            {
+                r = -1.983e+00;
+            }
+            if( w==66 )
+            {
+                r = -2.048e+00;
+            }
+            if( w==65 )
+            {
+                r = -2.115e+00;
+            }
+            if( w==64 )
+            {
+                r = -2.183e+00;
+            }
+            if( w==63 )
+            {
+                r = -2.253e+00;
+            }
+            if( w==62 )
+            {
+                r = -2.325e+00;
+            }
+            if( w==61 )
+            {
+                r = -2.398e+00;
+            }
+            if( w==60 )
+            {
+                r = -2.472e+00;
+            }
+            if( w==59 )
+            {
+                r = -2.548e+00;
+            }
+            if( w==58 )
+            {
+                r = -2.626e+00;
+            }
+            if( w==57 )
+            {
+                r = -2.706e+00;
+            }
+            if( w==56 )
+            {
+                r = -2.787e+00;
+            }
+            if( w==55 )
+            {
+                r = -2.870e+00;
+            }
+            if( w==54 )
+            {
+                r = -2.955e+00;
+            }
+            if( w==53 )
+            {
+                r = -3.042e+00;
+            }
+            if( w==52 )
+            {
+                r = -3.130e+00;
+            }
+            if( w==51 )
+            {
+                r = -3.220e+00;
+            }
+            if( w==50 )
+            {
+                r = -3.313e+00;
+            }
+            if( w==49 )
+            {
+                r = -3.407e+00;
+            }
+            if( w==48 )
+            {
+                r = -3.503e+00;
+            }
+            if( w==47 )
+            {
+                r = -3.601e+00;
+            }
+            if( w==46 )
+            {
+                r = -3.702e+00;
+            }
+            if( w==45 )
+            {
+                r = -3.804e+00;
+            }
+            if( w==44 )
+            {
+                r = -3.909e+00;
+            }
+            if( w==43 )
+            {
+                r = -4.015e+00;
+            }
+            if( w==42 )
+            {
+                r = -4.125e+00;
+            }
+            if( w==41 )
+            {
+                r = -4.236e+00;
+            }
+            if( w==40 )
+            {
+                r = -4.350e+00;
+            }
+            if( w==39 )
+            {
+                r = -4.466e+00;
+            }
+            if( w==38 )
+            {
+                r = -4.585e+00;
+            }
+            if( w==37 )
+            {
+                r = -4.706e+00;
+            }
+            if( w==36 )
+            {
+                r = -4.830e+00;
+            }
+            if( w==35 )
+            {
+                r = -4.957e+00;
+            }
+            if( w==34 )
+            {
+                r = -5.086e+00;
+            }
+            if( w==33 )
+            {
+                r = -5.219e+00;
+            }
+            if( w==32 )
+            {
+                r = -5.355e+00;
+            }
+            if( w==31 )
+            {
+                r = -5.493e+00;
+            }
+            if( w==30 )
+            {
+                r = -5.634e+00;
+            }
+            if( w==29 )
+            {
+                r = -5.780e+00;
+            }
+            if( w==28 )
+            {
+                r = -5.928e+00;
+            }
+            if( w==27 )
+            {
+                r = -6.080e+00;
+            }
+            if( w==26 )
+            {
+                r = -6.235e+00;
+            }
+            if( w==25 )
+            {
+                r = -6.394e+00;
+            }
+            if( w==24 )
+            {
+                r = -6.558e+00;
+            }
+            if( w==23 )
+            {
+                r = -6.726e+00;
+            }
+            if( w==22 )
+            {
+                r = -6.897e+00;
+            }
+            if( w==21 )
+            {
+                r = -7.074e+00;
+            }
+            if( w==20 )
+            {
+                r = -7.256e+00;
+            }
+            if( w==19 )
+            {
+                r = -7.443e+00;
+            }
+            if( w==18 )
+            {
+                r = -7.636e+00;
+            }
+            if( w==17 )
+            {
+                r = -7.837e+00;
+            }
+            if( w==16 )
+            {
+                r = -8.040e+00;
+            }
+            if( w==15 )
+            {
+                r = -8.250e+00;
+            }
+            if( w==14 )
+            {
+                r = -8.469e+00;
+            }
+            if( w==13 )
+            {
+                r = -8.692e+00;
+            }
+            if( w==12 )
+            {
+                r = -8.921e+00;
+            }
+            if( w==11 )
+            {
+                r = -9.162e+00;
+            }
+            if( w==10 )
+            {
+                r = -9.409e+00;
+            }
+            if( w==9 )
+            {
+                r = -9.673e+00;
+            }
+            if( w==8 )
+            {
+                r = -9.951e+00;
+            }
+            if( w==7 )
+            {
+                r = -1.023e+01;
+            }
+            if( w==6 )
+            {
+                r = -1.053e+01;
+            }
+            if( w==5 )
+            {
+                r = -1.087e+01;
+            }
+            if( w==4 )
+            {
+                r = -1.122e+01;
+            }
+            if( w==3 )
+            {
+                r = -1.156e+01;
+            }
+            if( w==2 )
+            {
+                r = -1.207e+01;
+            }
+            if( w==1 )
+            {
+                r = -1.248e+01;
+            }
+            if( w<=0 )
+            {
+                r = -1.317e+01;
+            }
+            result = r;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 20)
+        *************************************************************************/
+        private static double w20(double s)
+        {
+            double result = 0;
+            int w = 0;
+            double r = 0;
+
+            r = 0;
+            w = (int)Math.Round(-(2.678619e+01*s)+1.050000e+02);
+            if( w>=105 )
+            {
+                r = -6.787e-01;
+            }
+            if( w==104 )
+            {
+                r = -7.078e-01;
+            }
+            if( w==103 )
+            {
+                r = -7.378e-01;
+            }
+            if( w==102 )
+            {
+                r = -7.686e-01;
+            }
+            if( w==101 )
+            {
+                r = -8.004e-01;
+            }
+            if( w==100 )
+            {
+                r = -8.330e-01;
+            }
+            if( w==99 )
+            {
+                r = -8.665e-01;
+            }
+            if( w==98 )
+            {
+                r = -9.010e-01;
+            }
+            if( w==97 )
+            {
+                r = -9.363e-01;
+            }
+            if( w==96 )
+            {
+                r = -9.726e-01;
+            }
+            if( w==95 )
+            {
+                r = -1.010e+00;
+            }
+            if( w==94 )
+            {
+                r = -1.048e+00;
+            }
+            if( w==93 )
+            {
+                r = -1.087e+00;
+            }
+            if( w==92 )
+            {
+                r = -1.128e+00;
+            }
+            if( w==91 )
+            {
+                r = -1.169e+00;
+            }
+            if( w==90 )
+            {
+                r = -1.211e+00;
+            }
+            if( w==89 )
+            {
+                r = -1.254e+00;
+            }
+            if( w==88 )
+            {
+                r = -1.299e+00;
+            }
+            if( w==87 )
+            {
+                r = -1.344e+00;
+            }
+            if( w==86 )
+            {
+                r = -1.390e+00;
+            }
+            if( w==85 )
+            {
+                r = -1.438e+00;
+            }
+            if( w==84 )
+            {
+                r = -1.486e+00;
+            }
+            if( w==83 )
+            {
+                r = -1.536e+00;
+            }
+            if( w==82 )
+            {
+                r = -1.587e+00;
+            }
+            if( w==81 )
+            {
+                r = -1.639e+00;
+            }
+            if( w==80 )
+            {
+                r = -1.692e+00;
+            }
+            if( w==79 )
+            {
+                r = -1.746e+00;
+            }
+            if( w==78 )
+            {
+                r = -1.802e+00;
+            }
+            if( w==77 )
+            {
+                r = -1.859e+00;
+            }
+            if( w==76 )
+            {
+                r = -1.916e+00;
+            }
+            if( w==75 )
+            {
+                r = -1.976e+00;
+            }
+            if( w==74 )
+            {
+                r = -2.036e+00;
+            }
+            if( w==73 )
+            {
+                r = -2.098e+00;
+            }
+            if( w==72 )
+            {
+                r = -2.161e+00;
+            }
+            if( w==71 )
+            {
+                r = -2.225e+00;
+            }
+            if( w==70 )
+            {
+                r = -2.290e+00;
+            }
+            if( w==69 )
+            {
+                r = -2.357e+00;
+            }
+            if( w==68 )
+            {
+                r = -2.426e+00;
+            }
+            if( w==67 )
+            {
+                r = -2.495e+00;
+            }
+            if( w==66 )
+            {
+                r = -2.566e+00;
+            }
+            if( w==65 )
+            {
+                r = -2.639e+00;
+            }
+            if( w==64 )
+            {
+                r = -2.713e+00;
+            }
+            if( w==63 )
+            {
+                r = -2.788e+00;
+            }
+            if( w==62 )
+            {
+                r = -2.865e+00;
+            }
+            if( w==61 )
+            {
+                r = -2.943e+00;
+            }
+            if( w==60 )
+            {
+                r = -3.023e+00;
+            }
+            if( w==59 )
+            {
+                r = -3.104e+00;
+            }
+            if( w==58 )
+            {
+                r = -3.187e+00;
+            }
+            if( w==57 )
+            {
+                r = -3.272e+00;
+            }
+            if( w==56 )
+            {
+                r = -3.358e+00;
+            }
+            if( w==55 )
+            {
+                r = -3.446e+00;
+            }
+            if( w==54 )
+            {
+                r = -3.536e+00;
+            }
+            if( w==53 )
+            {
+                r = -3.627e+00;
+            }
+            if( w==52 )
+            {
+                r = -3.721e+00;
+            }
+            if( w==51 )
+            {
+                r = -3.815e+00;
+            }
+            if( w==50 )
+            {
+                r = -3.912e+00;
+            }
+            if( w==49 )
+            {
+                r = -4.011e+00;
+            }
+            if( w==48 )
+            {
+                r = -4.111e+00;
+            }
+            if( w==47 )
+            {
+                r = -4.214e+00;
+            }
+            if( w==46 )
+            {
+                r = -4.318e+00;
+            }
+            if( w==45 )
+            {
+                r = -4.425e+00;
+            }
+            if( w==44 )
+            {
+                r = -4.534e+00;
+            }
+            if( w==43 )
+            {
+                r = -4.644e+00;
+            }
+            if( w==42 )
+            {
+                r = -4.757e+00;
+            }
+            if( w==41 )
+            {
+                r = -4.872e+00;
+            }
+            if( w==40 )
+            {
+                r = -4.990e+00;
+            }
+            if( w==39 )
+            {
+                r = -5.109e+00;
+            }
+            if( w==38 )
+            {
+                r = -5.232e+00;
+            }
+            if( w==37 )
+            {
+                r = -5.356e+00;
+            }
+            if( w==36 )
+            {
+                r = -5.484e+00;
+            }
+            if( w==35 )
+            {
+                r = -5.614e+00;
+            }
+            if( w==34 )
+            {
+                r = -5.746e+00;
+            }
+            if( w==33 )
+            {
+                r = -5.882e+00;
+            }
+            if( w==32 )
+            {
+                r = -6.020e+00;
+            }
+            if( w==31 )
+            {
+                r = -6.161e+00;
+            }
+            if( w==30 )
+            {
+                r = -6.305e+00;
+            }
+            if( w==29 )
+            {
+                r = -6.453e+00;
+            }
+            if( w==28 )
+            {
+                r = -6.603e+00;
+            }
+            if( w==27 )
+            {
+                r = -6.757e+00;
+            }
+            if( w==26 )
+            {
+                r = -6.915e+00;
+            }
+            if( w==25 )
+            {
+                r = -7.076e+00;
+            }
+            if( w==24 )
+            {
+                r = -7.242e+00;
+            }
+            if( w==23 )
+            {
+                r = -7.411e+00;
+            }
+            if( w==22 )
+            {
+                r = -7.584e+00;
+            }
+            if( w==21 )
+            {
+                r = -7.763e+00;
+            }
+            if( w==20 )
+            {
+                r = -7.947e+00;
+            }
+            if( w==19 )
+            {
+                r = -8.136e+00;
+            }
+            if( w==18 )
+            {
+                r = -8.330e+00;
+            }
+            if( w==17 )
+            {
+                r = -8.530e+00;
+            }
+            if( w==16 )
+            {
+                r = -8.733e+00;
+            }
+            if( w==15 )
+            {
+                r = -8.943e+00;
+            }
+            if( w==14 )
+            {
+                r = -9.162e+00;
+            }
+            if( w==13 )
+            {
+                r = -9.386e+00;
+            }
+            if( w==12 )
+            {
+                r = -9.614e+00;
+            }
+            if( w==11 )
+            {
+                r = -9.856e+00;
+            }
+            if( w==10 )
+            {
+                r = -1.010e+01;
+            }
+            if( w==9 )
+            {
+                r = -1.037e+01;
+            }
+            if( w==8 )
+            {
+                r = -1.064e+01;
+            }
+            if( w==7 )
+            {
+                r = -1.092e+01;
+            }
+            if( w==6 )
+            {
+                r = -1.122e+01;
+            }
+            if( w==5 )
+            {
+                r = -1.156e+01;
+            }
+            if( w==4 )
+            {
+                r = -1.192e+01;
+            }
+            if( w==3 )
+            {
+                r = -1.225e+01;
+            }
+            if( w==2 )
+            {
+                r = -1.276e+01;
+            }
+            if( w==1 )
+            {
+                r = -1.317e+01;
+            }
+            if( w<=0 )
+            {
+                r = -1.386e+01;
+            }
+            result = r;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 21)
+        *************************************************************************/
+        private static double w21(double s)
+        {
+            double result = 0;
+            int w = 0;
+            double r = 0;
+
+            r = 0;
+            w = (int)Math.Round(-(2.877064e+01*s)+1.155000e+02);
+            if( w>=115 )
+            {
+                r = -6.931e-01;
+            }
+            if( w==114 )
+            {
+                r = -7.207e-01;
+            }
+            if( w==113 )
+            {
+                r = -7.489e-01;
+            }
+            if( w==112 )
+            {
+                r = -7.779e-01;
+            }
+            if( w==111 )
+            {
+                r = -8.077e-01;
+            }
+            if( w==110 )
+            {
+                r = -8.383e-01;
+            }
+            if( w==109 )
+            {
+                r = -8.697e-01;
+            }
+            if( w==108 )
+            {
+                r = -9.018e-01;
+            }
+            if( w==107 )
+            {
+                r = -9.348e-01;
+            }
+            if( w==106 )
+            {
+                r = -9.685e-01;
+            }
+            if( w==105 )
+            {
+                r = -1.003e+00;
+            }
+            if( w==104 )
+            {
+                r = -1.039e+00;
+            }
+            if( w==103 )
+            {
+                r = -1.075e+00;
+            }
+            if( w==102 )
+            {
+                r = -1.112e+00;
+            }
+            if( w==101 )
+            {
+                r = -1.150e+00;
+            }
+            if( w==100 )
+            {
+                r = -1.189e+00;
+            }
+            if( w==99 )
+            {
+                r = -1.229e+00;
+            }
+            if( w==98 )
+            {
+                r = -1.269e+00;
+            }
+            if( w==97 )
+            {
+                r = -1.311e+00;
+            }
+            if( w==96 )
+            {
+                r = -1.353e+00;
+            }
+            if( w==95 )
+            {
+                r = -1.397e+00;
+            }
+            if( w==94 )
+            {
+                r = -1.441e+00;
+            }
+            if( w==93 )
+            {
+                r = -1.486e+00;
+            }
+            if( w==92 )
+            {
+                r = -1.533e+00;
+            }
+            if( w==91 )
+            {
+                r = -1.580e+00;
+            }
+            if( w==90 )
+            {
+                r = -1.628e+00;
+            }
+            if( w==89 )
+            {
+                r = -1.677e+00;
+            }
+            if( w==88 )
+            {
+                r = -1.728e+00;
+            }
+            if( w==87 )
+            {
+                r = -1.779e+00;
+            }
+            if( w==86 )
+            {
+                r = -1.831e+00;
+            }
+            if( w==85 )
+            {
+                r = -1.884e+00;
+            }
+            if( w==84 )
+            {
+                r = -1.939e+00;
+            }
+            if( w==83 )
+            {
+                r = -1.994e+00;
+            }
+            if( w==82 )
+            {
+                r = -2.051e+00;
+            }
+            if( w==81 )
+            {
+                r = -2.108e+00;
+            }
+            if( w==80 )
+            {
+                r = -2.167e+00;
+            }
+            if( w==79 )
+            {
+                r = -2.227e+00;
+            }
+            if( w==78 )
+            {
+                r = -2.288e+00;
+            }
+            if( w==77 )
+            {
+                r = -2.350e+00;
+            }
+            if( w==76 )
+            {
+                r = -2.414e+00;
+            }
+            if( w==75 )
+            {
+                r = -2.478e+00;
+            }
+            if( w==74 )
+            {
+                r = -2.544e+00;
+            }
+            if( w==73 )
+            {
+                r = -2.611e+00;
+            }
+            if( w==72 )
+            {
+                r = -2.679e+00;
+            }
+            if( w==71 )
+            {
+                r = -2.748e+00;
+            }
+            if( w==70 )
+            {
+                r = -2.819e+00;
+            }
+            if( w==69 )
+            {
+                r = -2.891e+00;
+            }
+            if( w==68 )
+            {
+                r = -2.964e+00;
+            }
+            if( w==67 )
+            {
+                r = -3.039e+00;
+            }
+            if( w==66 )
+            {
+                r = -3.115e+00;
+            }
+            if( w==65 )
+            {
+                r = -3.192e+00;
+            }
+            if( w==64 )
+            {
+                r = -3.270e+00;
+            }
+            if( w==63 )
+            {
+                r = -3.350e+00;
+            }
+            if( w==62 )
+            {
+                r = -3.432e+00;
+            }
+            if( w==61 )
+            {
+                r = -3.515e+00;
+            }
+            if( w==60 )
+            {
+                r = -3.599e+00;
+            }
+            if( w==59 )
+            {
+                r = -3.685e+00;
+            }
+            if( w==58 )
+            {
+                r = -3.772e+00;
+            }
+            if( w==57 )
+            {
+                r = -3.861e+00;
+            }
+            if( w==56 )
+            {
+                r = -3.952e+00;
+            }
+            if( w==55 )
+            {
+                r = -4.044e+00;
+            }
+            if( w==54 )
+            {
+                r = -4.138e+00;
+            }
+            if( w==53 )
+            {
+                r = -4.233e+00;
+            }
+            if( w==52 )
+            {
+                r = -4.330e+00;
+            }
+            if( w==51 )
+            {
+                r = -4.429e+00;
+            }
+            if( w==50 )
+            {
+                r = -4.530e+00;
+            }
+            if( w==49 )
+            {
+                r = -4.632e+00;
+            }
+            if( w==48 )
+            {
+                r = -4.736e+00;
+            }
+            if( w==47 )
+            {
+                r = -4.842e+00;
+            }
+            if( w==46 )
+            {
+                r = -4.950e+00;
+            }
+            if( w==45 )
+            {
+                r = -5.060e+00;
+            }
+            if( w==44 )
+            {
+                r = -5.172e+00;
+            }
+            if( w==43 )
+            {
+                r = -5.286e+00;
+            }
+            if( w==42 )
+            {
+                r = -5.402e+00;
+            }
+            if( w==41 )
+            {
+                r = -5.520e+00;
+            }
+            if( w==40 )
+            {
+                r = -5.641e+00;
+            }
+            if( w==39 )
+            {
+                r = -5.763e+00;
+            }
+            if( w==38 )
+            {
+                r = -5.889e+00;
+            }
+            if( w==37 )
+            {
+                r = -6.016e+00;
+            }
+            if( w==36 )
+            {
+                r = -6.146e+00;
+            }
+            if( w==35 )
+            {
+                r = -6.278e+00;
+            }
+            if( w==34 )
+            {
+                r = -6.413e+00;
+            }
+            if( w==33 )
+            {
+                r = -6.551e+00;
+            }
+            if( w==32 )
+            {
+                r = -6.692e+00;
+            }
+            if( w==31 )
+            {
+                r = -6.835e+00;
+            }
+            if( w==30 )
+            {
+                r = -6.981e+00;
+            }
+            if( w==29 )
+            {
+                r = -7.131e+00;
+            }
+            if( w==28 )
+            {
+                r = -7.283e+00;
+            }
+            if( w==27 )
+            {
+                r = -7.439e+00;
+            }
+            if( w==26 )
+            {
+                r = -7.599e+00;
+            }
+            if( w==25 )
+            {
+                r = -7.762e+00;
+            }
+            if( w==24 )
+            {
+                r = -7.928e+00;
+            }
+            if( w==23 )
+            {
+                r = -8.099e+00;
+            }
+            if( w==22 )
+            {
+                r = -8.274e+00;
+            }
+            if( w==21 )
+            {
+                r = -8.454e+00;
+            }
+            if( w==20 )
+            {
+                r = -8.640e+00;
+            }
+            if( w==19 )
+            {
+                r = -8.829e+00;
+            }
+            if( w==18 )
+            {
+                r = -9.023e+00;
+            }
+            if( w==17 )
+            {
+                r = -9.223e+00;
+            }
+            if( w==16 )
+            {
+                r = -9.426e+00;
+            }
+            if( w==15 )
+            {
+                r = -9.636e+00;
+            }
+            if( w==14 )
+            {
+                r = -9.856e+00;
+            }
+            if( w==13 )
+            {
+                r = -1.008e+01;
+            }
+            if( w==12 )
+            {
+                r = -1.031e+01;
+            }
+            if( w==11 )
+            {
+                r = -1.055e+01;
+            }
+            if( w==10 )
+            {
+                r = -1.079e+01;
+            }
+            if( w==9 )
+            {
+                r = -1.106e+01;
+            }
+            if( w==8 )
+            {
+                r = -1.134e+01;
+            }
+            if( w==7 )
+            {
+                r = -1.161e+01;
+            }
+            if( w==6 )
+            {
+                r = -1.192e+01;
+            }
+            if( w==5 )
+            {
+                r = -1.225e+01;
+            }
+            if( w==4 )
+            {
+                r = -1.261e+01;
+            }
+            if( w==3 )
+            {
+                r = -1.295e+01;
+            }
+            if( w==2 )
+            {
+                r = -1.346e+01;
+            }
+            if( w==1 )
+            {
+                r = -1.386e+01;
+            }
+            if( w<=0 )
+            {
+                r = -1.456e+01;
+            }
+            result = r;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 22)
+        *************************************************************************/
+        private static double w22(double s)
+        {
+            double result = 0;
+            int w = 0;
+            double r = 0;
+
+            r = 0;
+            w = (int)Math.Round(-(3.080179e+01*s)+1.265000e+02);
+            if( w>=126 )
+            {
+                r = -6.931e-01;
+            }
+            if( w==125 )
+            {
+                r = -7.189e-01;
+            }
+            if( w==124 )
+            {
+                r = -7.452e-01;
+            }
+            if( w==123 )
+            {
+                r = -7.722e-01;
+            }
+            if( w==122 )
+            {
+                r = -7.999e-01;
+            }
+            if( w==121 )
+            {
+                r = -8.283e-01;
+            }
+            if( w==120 )
+            {
+                r = -8.573e-01;
+            }
+            if( w==119 )
+            {
+                r = -8.871e-01;
+            }
+            if( w==118 )
+            {
+                r = -9.175e-01;
+            }
+            if( w==117 )
+            {
+                r = -9.486e-01;
+            }
+            if( w==116 )
+            {
+                r = -9.805e-01;
+            }
+            if( w==115 )
+            {
+                r = -1.013e+00;
+            }
+            if( w==114 )
+            {
+                r = -1.046e+00;
+            }
+            if( w==113 )
+            {
+                r = -1.080e+00;
+            }
+            if( w==112 )
+            {
+                r = -1.115e+00;
+            }
+            if( w==111 )
+            {
+                r = -1.151e+00;
+            }
+            if( w==110 )
+            {
+                r = -1.187e+00;
+            }
+            if( w==109 )
+            {
+                r = -1.224e+00;
+            }
+            if( w==108 )
+            {
+                r = -1.262e+00;
+            }
+            if( w==107 )
+            {
+                r = -1.301e+00;
+            }
+            if( w==106 )
+            {
+                r = -1.340e+00;
+            }
+            if( w==105 )
+            {
+                r = -1.381e+00;
+            }
+            if( w==104 )
+            {
+                r = -1.422e+00;
+            }
+            if( w==103 )
+            {
+                r = -1.464e+00;
+            }
+            if( w==102 )
+            {
+                r = -1.506e+00;
+            }
+            if( w==101 )
+            {
+                r = -1.550e+00;
+            }
+            if( w==100 )
+            {
+                r = -1.594e+00;
+            }
+            if( w==99 )
+            {
+                r = -1.640e+00;
+            }
+            if( w==98 )
+            {
+                r = -1.686e+00;
+            }
+            if( w==97 )
+            {
+                r = -1.733e+00;
+            }
+            if( w==96 )
+            {
+                r = -1.781e+00;
+            }
+            if( w==95 )
+            {
+                r = -1.830e+00;
+            }
+            if( w==94 )
+            {
+                r = -1.880e+00;
+            }
+            if( w==93 )
+            {
+                r = -1.930e+00;
+            }
+            if( w==92 )
+            {
+                r = -1.982e+00;
+            }
+            if( w==91 )
+            {
+                r = -2.034e+00;
+            }
+            if( w==90 )
+            {
+                r = -2.088e+00;
+            }
+            if( w==89 )
+            {
+                r = -2.142e+00;
+            }
+            if( w==88 )
+            {
+                r = -2.198e+00;
+            }
+            if( w==87 )
+            {
+                r = -2.254e+00;
+            }
+            if( w==86 )
+            {
+                r = -2.312e+00;
+            }
+            if( w==85 )
+            {
+                r = -2.370e+00;
+            }
+            if( w==84 )
+            {
+                r = -2.429e+00;
+            }
+            if( w==83 )
+            {
+                r = -2.490e+00;
+            }
+            if( w==82 )
+            {
+                r = -2.551e+00;
+            }
+            if( w==81 )
+            {
+                r = -2.614e+00;
+            }
+            if( w==80 )
+            {
+                r = -2.677e+00;
+            }
+            if( w==79 )
+            {
+                r = -2.742e+00;
+            }
+            if( w==78 )
+            {
+                r = -2.808e+00;
+            }
+            if( w==77 )
+            {
+                r = -2.875e+00;
+            }
+            if( w==76 )
+            {
+                r = -2.943e+00;
+            }
+            if( w==75 )
+            {
+                r = -3.012e+00;
+            }
+            if( w==74 )
+            {
+                r = -3.082e+00;
+            }
+            if( w==73 )
+            {
+                r = -3.153e+00;
+            }
+            if( w==72 )
+            {
+                r = -3.226e+00;
+            }
+            if( w==71 )
+            {
+                r = -3.300e+00;
+            }
+            if( w==70 )
+            {
+                r = -3.375e+00;
+            }
+            if( w==69 )
+            {
+                r = -3.451e+00;
+            }
+            if( w==68 )
+            {
+                r = -3.529e+00;
+            }
+            if( w==67 )
+            {
+                r = -3.607e+00;
+            }
+            if( w==66 )
+            {
+                r = -3.687e+00;
+            }
+            if( w==65 )
+            {
+                r = -3.769e+00;
+            }
+            if( w==64 )
+            {
+                r = -3.851e+00;
+            }
+            if( w==63 )
+            {
+                r = -3.935e+00;
+            }
+            if( w==62 )
+            {
+                r = -4.021e+00;
+            }
+            if( w==61 )
+            {
+                r = -4.108e+00;
+            }
+            if( w==60 )
+            {
+                r = -4.196e+00;
+            }
+            if( w==59 )
+            {
+                r = -4.285e+00;
+            }
+            if( w==58 )
+            {
+                r = -4.376e+00;
+            }
+            if( w==57 )
+            {
+                r = -4.469e+00;
+            }
+            if( w==56 )
+            {
+                r = -4.563e+00;
+            }
+            if( w==55 )
+            {
+                r = -4.659e+00;
+            }
+            if( w==54 )
+            {
+                r = -4.756e+00;
+            }
+            if( w==53 )
+            {
+                r = -4.855e+00;
+            }
+            if( w==52 )
+            {
+                r = -4.955e+00;
+            }
+            if( w==51 )
+            {
+                r = -5.057e+00;
+            }
+            if( w==50 )
+            {
+                r = -5.161e+00;
+            }
+            if( w==49 )
+            {
+                r = -5.266e+00;
+            }
+            if( w==48 )
+            {
+                r = -5.374e+00;
+            }
+            if( w==47 )
+            {
+                r = -5.483e+00;
+            }
+            if( w==46 )
+            {
+                r = -5.594e+00;
+            }
+            if( w==45 )
+            {
+                r = -5.706e+00;
+            }
+            if( w==44 )
+            {
+                r = -5.821e+00;
+            }
+            if( w==43 )
+            {
+                r = -5.938e+00;
+            }
+            if( w==42 )
+            {
+                r = -6.057e+00;
+            }
+            if( w==41 )
+            {
+                r = -6.177e+00;
+            }
+            if( w==40 )
+            {
+                r = -6.300e+00;
+            }
+            if( w==39 )
+            {
+                r = -6.426e+00;
+            }
+            if( w==38 )
+            {
+                r = -6.553e+00;
+            }
+            if( w==37 )
+            {
+                r = -6.683e+00;
+            }
+            if( w==36 )
+            {
+                r = -6.815e+00;
+            }
+            if( w==35 )
+            {
+                r = -6.949e+00;
+            }
+            if( w==34 )
+            {
+                r = -7.086e+00;
+            }
+            if( w==33 )
+            {
+                r = -7.226e+00;
+            }
+            if( w==32 )
+            {
+                r = -7.368e+00;
+            }
+            if( w==31 )
+            {
+                r = -7.513e+00;
+            }
+            if( w==30 )
+            {
+                r = -7.661e+00;
+            }
+            if( w==29 )
+            {
+                r = -7.813e+00;
+            }
+            if( w==28 )
+            {
+                r = -7.966e+00;
+            }
+            if( w==27 )
+            {
+                r = -8.124e+00;
+            }
+            if( w==26 )
+            {
+                r = -8.285e+00;
+            }
+            if( w==25 )
+            {
+                r = -8.449e+00;
+            }
+            if( w==24 )
+            {
+                r = -8.617e+00;
+            }
+            if( w==23 )
+            {
+                r = -8.789e+00;
+            }
+            if( w==22 )
+            {
+                r = -8.965e+00;
+            }
+            if( w==21 )
+            {
+                r = -9.147e+00;
+            }
+            if( w==20 )
+            {
+                r = -9.333e+00;
+            }
+            if( w==19 )
+            {
+                r = -9.522e+00;
+            }
+            if( w==18 )
+            {
+                r = -9.716e+00;
+            }
+            if( w==17 )
+            {
+                r = -9.917e+00;
+            }
+            if( w==16 )
+            {
+                r = -1.012e+01;
+            }
+            if( w==15 )
+            {
+                r = -1.033e+01;
+            }
+            if( w==14 )
+            {
+                r = -1.055e+01;
+            }
+            if( w==13 )
+            {
+                r = -1.077e+01;
+            }
+            if( w==12 )
+            {
+                r = -1.100e+01;
+            }
+            if( w==11 )
+            {
+                r = -1.124e+01;
+            }
+            if( w==10 )
+            {
+                r = -1.149e+01;
+            }
+            if( w==9 )
+            {
+                r = -1.175e+01;
+            }
+            if( w==8 )
+            {
+                r = -1.203e+01;
+            }
+            if( w==7 )
+            {
+                r = -1.230e+01;
+            }
+            if( w==6 )
+            {
+                r = -1.261e+01;
+            }
+            if( w==5 )
+            {
+                r = -1.295e+01;
+            }
+            if( w==4 )
+            {
+                r = -1.330e+01;
+            }
+            if( w==3 )
+            {
+                r = -1.364e+01;
+            }
+            if( w==2 )
+            {
+                r = -1.415e+01;
+            }
+            if( w==1 )
+            {
+                r = -1.456e+01;
+            }
+            if( w<=0 )
+            {
+                r = -1.525e+01;
+            }
+            result = r;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 23)
+        *************************************************************************/
+        private static double w23(double s)
+        {
+            double result = 0;
+            int w = 0;
+            double r = 0;
+
+            r = 0;
+            w = (int)Math.Round(-(3.287856e+01*s)+1.380000e+02);
+            if( w>=138 )
+            {
+                r = -6.813e-01;
+            }
+            if( w==137 )
+            {
+                r = -7.051e-01;
+            }
+            if( w==136 )
+            {
+                r = -7.295e-01;
+            }
+            if( w==135 )
+            {
+                r = -7.544e-01;
+            }
+            if( w==134 )
+            {
+                r = -7.800e-01;
+            }
+            if( w==133 )
+            {
+                r = -8.061e-01;
+            }
+            if( w==132 )
+            {
+                r = -8.328e-01;
+            }
+            if( w==131 )
+            {
+                r = -8.601e-01;
+            }
+            if( w==130 )
+            {
+                r = -8.880e-01;
+            }
+            if( w==129 )
+            {
+                r = -9.166e-01;
+            }
+            if( w==128 )
+            {
+                r = -9.457e-01;
+            }
+            if( w==127 )
+            {
+                r = -9.755e-01;
+            }
+            if( w==126 )
+            {
+                r = -1.006e+00;
+            }
+            if( w==125 )
+            {
+                r = -1.037e+00;
+            }
+            if( w==124 )
+            {
+                r = -1.069e+00;
+            }
+            if( w==123 )
+            {
+                r = -1.101e+00;
+            }
+            if( w==122 )
+            {
+                r = -1.134e+00;
+            }
+            if( w==121 )
+            {
+                r = -1.168e+00;
+            }
+            if( w==120 )
+            {
+                r = -1.202e+00;
+            }
+            if( w==119 )
+            {
+                r = -1.237e+00;
+            }
+            if( w==118 )
+            {
+                r = -1.273e+00;
+            }
+            if( w==117 )
+            {
+                r = -1.309e+00;
+            }
+            if( w==116 )
+            {
+                r = -1.347e+00;
+            }
+            if( w==115 )
+            {
+                r = -1.384e+00;
+            }
+            if( w==114 )
+            {
+                r = -1.423e+00;
+            }
+            if( w==113 )
+            {
+                r = -1.462e+00;
+            }
+            if( w==112 )
+            {
+                r = -1.502e+00;
+            }
+            if( w==111 )
+            {
+                r = -1.543e+00;
+            }
+            if( w==110 )
+            {
+                r = -1.585e+00;
+            }
+            if( w==109 )
+            {
+                r = -1.627e+00;
+            }
+            if( w==108 )
+            {
+                r = -1.670e+00;
+            }
+            if( w==107 )
+            {
+                r = -1.714e+00;
+            }
+            if( w==106 )
+            {
+                r = -1.758e+00;
+            }
+            if( w==105 )
+            {
+                r = -1.804e+00;
+            }
+            if( w==104 )
+            {
+                r = -1.850e+00;
+            }
+            if( w==103 )
+            {
+                r = -1.897e+00;
+            }
+            if( w==102 )
+            {
+                r = -1.944e+00;
+            }
+            if( w==101 )
+            {
+                r = -1.993e+00;
+            }
+            if( w==100 )
+            {
+                r = -2.042e+00;
+            }
+            if( w==99 )
+            {
+                r = -2.093e+00;
+            }
+            if( w==98 )
+            {
+                r = -2.144e+00;
+            }
+            if( w==97 )
+            {
+                r = -2.195e+00;
+            }
+            if( w==96 )
+            {
+                r = -2.248e+00;
+            }
+            if( w==95 )
+            {
+                r = -2.302e+00;
+            }
+            if( w==94 )
+            {
+                r = -2.356e+00;
+            }
+            if( w==93 )
+            {
+                r = -2.412e+00;
+            }
+            if( w==92 )
+            {
+                r = -2.468e+00;
+            }
+            if( w==91 )
+            {
+                r = -2.525e+00;
+            }
+            if( w==90 )
+            {
+                r = -2.583e+00;
+            }
+            if( w==89 )
+            {
+                r = -2.642e+00;
+            }
+            if( w==88 )
+            {
+                r = -2.702e+00;
+            }
+            if( w==87 )
+            {
+                r = -2.763e+00;
+            }
+            if( w==86 )
+            {
+                r = -2.825e+00;
+            }
+            if( w==85 )
+            {
+                r = -2.888e+00;
+            }
+            if( w==84 )
+            {
+                r = -2.951e+00;
+            }
+            if( w==83 )
+            {
+                r = -3.016e+00;
+            }
+            if( w==82 )
+            {
+                r = -3.082e+00;
+            }
+            if( w==81 )
+            {
+                r = -3.149e+00;
+            }
+            if( w==80 )
+            {
+                r = -3.216e+00;
+            }
+            if( w==79 )
+            {
+                r = -3.285e+00;
+            }
+            if( w==78 )
+            {
+                r = -3.355e+00;
+            }
+            if( w==77 )
+            {
+                r = -3.426e+00;
+            }
+            if( w==76 )
+            {
+                r = -3.498e+00;
+            }
+            if( w==75 )
+            {
+                r = -3.571e+00;
+            }
+            if( w==74 )
+            {
+                r = -3.645e+00;
+            }
+            if( w==73 )
+            {
+                r = -3.721e+00;
+            }
+            if( w==72 )
+            {
+                r = -3.797e+00;
+            }
+            if( w==71 )
+            {
+                r = -3.875e+00;
+            }
+            if( w==70 )
+            {
+                r = -3.953e+00;
+            }
+            if( w==69 )
+            {
+                r = -4.033e+00;
+            }
+            if( w==68 )
+            {
+                r = -4.114e+00;
+            }
+            if( w==67 )
+            {
+                r = -4.197e+00;
+            }
+            if( w==66 )
+            {
+                r = -4.280e+00;
+            }
+            if( w==65 )
+            {
+                r = -4.365e+00;
+            }
+            if( w==64 )
+            {
+                r = -4.451e+00;
+            }
+            if( w==63 )
+            {
+                r = -4.539e+00;
+            }
+            if( w==62 )
+            {
+                r = -4.628e+00;
+            }
+            if( w==61 )
+            {
+                r = -4.718e+00;
+            }
+            if( w==60 )
+            {
+                r = -4.809e+00;
+            }
+            if( w==59 )
+            {
+                r = -4.902e+00;
+            }
+            if( w==58 )
+            {
+                r = -4.996e+00;
+            }
+            if( w==57 )
+            {
+                r = -5.092e+00;
+            }
+            if( w==56 )
+            {
+                r = -5.189e+00;
+            }
+            if( w==55 )
+            {
+                r = -5.287e+00;
+            }
+            if( w==54 )
+            {
+                r = -5.388e+00;
+            }
+            if( w==53 )
+            {
+                r = -5.489e+00;
+            }
+            if( w==52 )
+            {
+                r = -5.592e+00;
+            }
+            if( w==51 )
+            {
+                r = -5.697e+00;
+            }
+            if( w==50 )
+            {
+                r = -5.804e+00;
+            }
+            if( w==49 )
+            {
+                r = -5.912e+00;
+            }
+            if( w==48 )
+            {
+                r = -6.022e+00;
+            }
+            if( w==47 )
+            {
+                r = -6.133e+00;
+            }
+            if( w==46 )
+            {
+                r = -6.247e+00;
+            }
+            if( w==45 )
+            {
+                r = -6.362e+00;
+            }
+            if( w==44 )
+            {
+                r = -6.479e+00;
+            }
+            if( w==43 )
+            {
+                r = -6.598e+00;
+            }
+            if( w==42 )
+            {
+                r = -6.719e+00;
+            }
+            if( w==41 )
+            {
+                r = -6.842e+00;
+            }
+            if( w==40 )
+            {
+                r = -6.967e+00;
+            }
+            if( w==39 )
+            {
+                r = -7.094e+00;
+            }
+            if( w==38 )
+            {
+                r = -7.224e+00;
+            }
+            if( w==37 )
+            {
+                r = -7.355e+00;
+            }
+            if( w==36 )
+            {
+                r = -7.489e+00;
+            }
+            if( w==35 )
+            {
+                r = -7.625e+00;
+            }
+            if( w==34 )
+            {
+                r = -7.764e+00;
+            }
+            if( w==33 )
+            {
+                r = -7.905e+00;
+            }
+            if( w==32 )
+            {
+                r = -8.049e+00;
+            }
+            if( w==31 )
+            {
+                r = -8.196e+00;
+            }
+            if( w==30 )
+            {
+                r = -8.345e+00;
+            }
+            if( w==29 )
+            {
+                r = -8.498e+00;
+            }
+            if( w==28 )
+            {
+                r = -8.653e+00;
+            }
+            if( w==27 )
+            {
+                r = -8.811e+00;
+            }
+            if( w==26 )
+            {
+                r = -8.974e+00;
+            }
+            if( w==25 )
+            {
+                r = -9.139e+00;
+            }
+            if( w==24 )
+            {
+                r = -9.308e+00;
+            }
+            if( w==23 )
+            {
+                r = -9.481e+00;
+            }
+            if( w==22 )
+            {
+                r = -9.658e+00;
+            }
+            if( w==21 )
+            {
+                r = -9.840e+00;
+            }
+            if( w==20 )
+            {
+                r = -1.003e+01;
+            }
+            if( w==19 )
+            {
+                r = -1.022e+01;
+            }
+            if( w==18 )
+            {
+                r = -1.041e+01;
+            }
+            if( w==17 )
+            {
+                r = -1.061e+01;
+            }
+            if( w==16 )
+            {
+                r = -1.081e+01;
+            }
+            if( w==15 )
+            {
+                r = -1.102e+01;
+            }
+            if( w==14 )
+            {
+                r = -1.124e+01;
+            }
+            if( w==13 )
+            {
+                r = -1.147e+01;
+            }
+            if( w==12 )
+            {
+                r = -1.169e+01;
+            }
+            if( w==11 )
+            {
+                r = -1.194e+01;
+            }
+            if( w==10 )
+            {
+                r = -1.218e+01;
+            }
+            if( w==9 )
+            {
+                r = -1.245e+01;
+            }
+            if( w==8 )
+            {
+                r = -1.272e+01;
+            }
+            if( w==7 )
+            {
+                r = -1.300e+01;
+            }
+            if( w==6 )
+            {
+                r = -1.330e+01;
+            }
+            if( w==5 )
+            {
+                r = -1.364e+01;
+            }
+            if( w==4 )
+            {
+                r = -1.400e+01;
+            }
+            if( w==3 )
+            {
+                r = -1.433e+01;
+            }
+            if( w==2 )
+            {
+                r = -1.484e+01;
+            }
+            if( w==1 )
+            {
+                r = -1.525e+01;
+            }
+            if( w<=0 )
+            {
+                r = -1.594e+01;
+            }
+            result = r;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 24)
+        *************************************************************************/
+        private static double w24(double s)
+        {
+            double result = 0;
+            int w = 0;
+            double r = 0;
+
+            r = 0;
+            w = (int)Math.Round(-(3.500000e+01*s)+1.500000e+02);
+            if( w>=150 )
+            {
+                r = -6.820e-01;
+            }
+            if( w==149 )
+            {
+                r = -7.044e-01;
+            }
+            if( w==148 )
+            {
+                r = -7.273e-01;
+            }
+            if( w==147 )
+            {
+                r = -7.507e-01;
+            }
+            if( w==146 )
+            {
+                r = -7.746e-01;
+            }
+            if( w==145 )
+            {
+                r = -7.990e-01;
+            }
+            if( w==144 )
+            {
+                r = -8.239e-01;
+            }
+            if( w==143 )
+            {
+                r = -8.494e-01;
+            }
+            if( w==142 )
+            {
+                r = -8.754e-01;
+            }
+            if( w==141 )
+            {
+                r = -9.020e-01;
+            }
+            if( w==140 )
+            {
+                r = -9.291e-01;
+            }
+            if( w==139 )
+            {
+                r = -9.567e-01;
+            }
+            if( w==138 )
+            {
+                r = -9.849e-01;
+            }
+            if( w==137 )
+            {
+                r = -1.014e+00;
+            }
+            if( w==136 )
+            {
+                r = -1.043e+00;
+            }
+            if( w==135 )
+            {
+                r = -1.073e+00;
+            }
+            if( w==134 )
+            {
+                r = -1.103e+00;
+            }
+            if( w==133 )
+            {
+                r = -1.135e+00;
+            }
+            if( w==132 )
+            {
+                r = -1.166e+00;
+            }
+            if( w==131 )
+            {
+                r = -1.198e+00;
+            }
+            if( w==130 )
+            {
+                r = -1.231e+00;
+            }
+            if( w==129 )
+            {
+                r = -1.265e+00;
+            }
+            if( w==128 )
+            {
+                r = -1.299e+00;
+            }
+            if( w==127 )
+            {
+                r = -1.334e+00;
+            }
+            if( w==126 )
+            {
+                r = -1.369e+00;
+            }
+            if( w==125 )
+            {
+                r = -1.405e+00;
+            }
+            if( w==124 )
+            {
+                r = -1.441e+00;
+            }
+            if( w==123 )
+            {
+                r = -1.479e+00;
+            }
+            if( w==122 )
+            {
+                r = -1.517e+00;
+            }
+            if( w==121 )
+            {
+                r = -1.555e+00;
+            }
+            if( w==120 )
+            {
+                r = -1.594e+00;
+            }
+            if( w==119 )
+            {
+                r = -1.634e+00;
+            }
+            if( w==118 )
+            {
+                r = -1.675e+00;
+            }
+            if( w==117 )
+            {
+                r = -1.716e+00;
+            }
+            if( w==116 )
+            {
+                r = -1.758e+00;
+            }
+            if( w==115 )
+            {
+                r = -1.800e+00;
+            }
+            if( w==114 )
+            {
+                r = -1.844e+00;
+            }
+            if( w==113 )
+            {
+                r = -1.888e+00;
+            }
+            if( w==112 )
+            {
+                r = -1.932e+00;
+            }
+            if( w==111 )
+            {
+                r = -1.978e+00;
+            }
+            if( w==110 )
+            {
+                r = -2.024e+00;
+            }
+            if( w==109 )
+            {
+                r = -2.070e+00;
+            }
+            if( w==108 )
+            {
+                r = -2.118e+00;
+            }
+            if( w==107 )
+            {
+                r = -2.166e+00;
+            }
+            if( w==106 )
+            {
+                r = -2.215e+00;
+            }
+            if( w==105 )
+            {
+                r = -2.265e+00;
+            }
+            if( w==104 )
+            {
+                r = -2.316e+00;
+            }
+            if( w==103 )
+            {
+                r = -2.367e+00;
+            }
+            if( w==102 )
+            {
+                r = -2.419e+00;
+            }
+            if( w==101 )
+            {
+                r = -2.472e+00;
+            }
+            if( w==100 )
+            {
+                r = -2.526e+00;
+            }
+            if( w==99 )
+            {
+                r = -2.580e+00;
+            }
+            if( w==98 )
+            {
+                r = -2.636e+00;
+            }
+            if( w==97 )
+            {
+                r = -2.692e+00;
+            }
+            if( w==96 )
+            {
+                r = -2.749e+00;
+            }
+            if( w==95 )
+            {
+                r = -2.806e+00;
+            }
+            if( w==94 )
+            {
+                r = -2.865e+00;
+            }
+            if( w==93 )
+            {
+                r = -2.925e+00;
+            }
+            if( w==92 )
+            {
+                r = -2.985e+00;
+            }
+            if( w==91 )
+            {
+                r = -3.046e+00;
+            }
+            if( w==90 )
+            {
+                r = -3.108e+00;
+            }
+            if( w==89 )
+            {
+                r = -3.171e+00;
+            }
+            if( w==88 )
+            {
+                r = -3.235e+00;
+            }
+            if( w==87 )
+            {
+                r = -3.300e+00;
+            }
+            if( w==86 )
+            {
+                r = -3.365e+00;
+            }
+            if( w==85 )
+            {
+                r = -3.432e+00;
+            }
+            if( w==84 )
+            {
+                r = -3.499e+00;
+            }
+            if( w==83 )
+            {
+                r = -3.568e+00;
+            }
+            if( w==82 )
+            {
+                r = -3.637e+00;
+            }
+            if( w==81 )
+            {
+                r = -3.708e+00;
+            }
+            if( w==80 )
+            {
+                r = -3.779e+00;
+            }
+            if( w==79 )
+            {
+                r = -3.852e+00;
+            }
+            if( w==78 )
+            {
+                r = -3.925e+00;
+            }
+            if( w==77 )
+            {
+                r = -4.000e+00;
+            }
+            if( w==76 )
+            {
+                r = -4.075e+00;
+            }
+            if( w==75 )
+            {
+                r = -4.151e+00;
+            }
+            if( w==74 )
+            {
+                r = -4.229e+00;
+            }
+            if( w==73 )
+            {
+                r = -4.308e+00;
+            }
+            if( w==72 )
+            {
+                r = -4.387e+00;
+            }
+            if( w==71 )
+            {
+                r = -4.468e+00;
+            }
+            if( w==70 )
+            {
+                r = -4.550e+00;
+            }
+            if( w==69 )
+            {
+                r = -4.633e+00;
+            }
+            if( w==68 )
+            {
+                r = -4.718e+00;
+            }
+            if( w==67 )
+            {
+                r = -4.803e+00;
+            }
+            if( w==66 )
+            {
+                r = -4.890e+00;
+            }
+            if( w==65 )
+            {
+                r = -4.978e+00;
+            }
+            if( w==64 )
+            {
+                r = -5.067e+00;
+            }
+            if( w==63 )
+            {
+                r = -5.157e+00;
+            }
+            if( w==62 )
+            {
+                r = -5.249e+00;
+            }
+            if( w==61 )
+            {
+                r = -5.342e+00;
+            }
+            if( w==60 )
+            {
+                r = -5.436e+00;
+            }
+            if( w==59 )
+            {
+                r = -5.531e+00;
+            }
+            if( w==58 )
+            {
+                r = -5.628e+00;
+            }
+            if( w==57 )
+            {
+                r = -5.727e+00;
+            }
+            if( w==56 )
+            {
+                r = -5.826e+00;
+            }
+            if( w==55 )
+            {
+                r = -5.927e+00;
+            }
+            if( w==54 )
+            {
+                r = -6.030e+00;
+            }
+            if( w==53 )
+            {
+                r = -6.134e+00;
+            }
+            if( w==52 )
+            {
+                r = -6.240e+00;
+            }
+            if( w==51 )
+            {
+                r = -6.347e+00;
+            }
+            if( w==50 )
+            {
+                r = -6.456e+00;
+            }
+            if( w==49 )
+            {
+                r = -6.566e+00;
+            }
+            if( w==48 )
+            {
+                r = -6.678e+00;
+            }
+            if( w==47 )
+            {
+                r = -6.792e+00;
+            }
+            if( w==46 )
+            {
+                r = -6.907e+00;
+            }
+            if( w==45 )
+            {
+                r = -7.025e+00;
+            }
+            if( w==44 )
+            {
+                r = -7.144e+00;
+            }
+            if( w==43 )
+            {
+                r = -7.265e+00;
+            }
+            if( w==42 )
+            {
+                r = -7.387e+00;
+            }
+            if( w==41 )
+            {
+                r = -7.512e+00;
+            }
+            if( w==40 )
+            {
+                r = -7.639e+00;
+            }
+            if( w==39 )
+            {
+                r = -7.768e+00;
+            }
+            if( w==38 )
+            {
+                r = -7.899e+00;
+            }
+            if( w==37 )
+            {
+                r = -8.032e+00;
+            }
+            if( w==36 )
+            {
+                r = -8.167e+00;
+            }
+            if( w==35 )
+            {
+                r = -8.305e+00;
+            }
+            if( w==34 )
+            {
+                r = -8.445e+00;
+            }
+            if( w==33 )
+            {
+                r = -8.588e+00;
+            }
+            if( w==32 )
+            {
+                r = -8.733e+00;
+            }
+            if( w==31 )
+            {
+                r = -8.881e+00;
+            }
+            if( w==30 )
+            {
+                r = -9.031e+00;
+            }
+            if( w==29 )
+            {
+                r = -9.185e+00;
+            }
+            if( w==28 )
+            {
+                r = -9.341e+00;
+            }
+            if( w==27 )
+            {
+                r = -9.501e+00;
+            }
+            if( w==26 )
+            {
+                r = -9.664e+00;
+            }
+            if( w==25 )
+            {
+                r = -9.830e+00;
+            }
+            if( w==24 )
+            {
+                r = -1.000e+01;
+            }
+            if( w==23 )
+            {
+                r = -1.017e+01;
+            }
+            if( w==22 )
+            {
+                r = -1.035e+01;
+            }
+            if( w==21 )
+            {
+                r = -1.053e+01;
+            }
+            if( w==20 )
+            {
+                r = -1.072e+01;
+            }
+            if( w==19 )
+            {
+                r = -1.091e+01;
+            }
+            if( w==18 )
+            {
+                r = -1.110e+01;
+            }
+            if( w==17 )
+            {
+                r = -1.130e+01;
+            }
+            if( w==16 )
+            {
+                r = -1.151e+01;
+            }
+            if( w==15 )
+            {
+                r = -1.172e+01;
+            }
+            if( w==14 )
+            {
+                r = -1.194e+01;
+            }
+            if( w==13 )
+            {
+                r = -1.216e+01;
+            }
+            if( w==12 )
+            {
+                r = -1.239e+01;
+            }
+            if( w==11 )
+            {
+                r = -1.263e+01;
+            }
+            if( w==10 )
+            {
+                r = -1.287e+01;
+            }
+            if( w==9 )
+            {
+                r = -1.314e+01;
+            }
+            if( w==8 )
+            {
+                r = -1.342e+01;
+            }
+            if( w==7 )
+            {
+                r = -1.369e+01;
+            }
+            if( w==6 )
+            {
+                r = -1.400e+01;
+            }
+            if( w==5 )
+            {
+                r = -1.433e+01;
+            }
+            if( w==4 )
+            {
+                r = -1.469e+01;
+            }
+            if( w==3 )
+            {
+                r = -1.503e+01;
+            }
+            if( w==2 )
+            {
+                r = -1.554e+01;
+            }
+            if( w==1 )
+            {
+                r = -1.594e+01;
+            }
+            if( w<=0 )
+            {
+                r = -1.664e+01;
+            }
+            result = r;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 25)
+        *************************************************************************/
+        private static double w25(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/4.000000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            wcheb(x, -5.150509e+00, ref tj, ref tj1, ref result);
+            wcheb(x, -5.695528e+00, ref tj, ref tj1, ref result);
+            wcheb(x, -1.437637e+00, ref tj, ref tj1, ref result);
+            wcheb(x, -2.611906e-01, ref tj, ref tj1, ref result);
+            wcheb(x, -7.625722e-02, ref tj, ref tj1, ref result);
+            wcheb(x, -2.579892e-02, ref tj, ref tj1, ref result);
+            wcheb(x, -1.086876e-02, ref tj, ref tj1, ref result);
+            wcheb(x, -2.906543e-03, ref tj, ref tj1, ref result);
+            wcheb(x, -2.354881e-03, ref tj, ref tj1, ref result);
+            wcheb(x, 1.007195e-04, ref tj, ref tj1, ref result);
+            wcheb(x, -8.437327e-04, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 26)
+        *************************************************************************/
+        private static double w26(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/4.000000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            wcheb(x, -5.117622e+00, ref tj, ref tj1, ref result);
+            wcheb(x, -5.635159e+00, ref tj, ref tj1, ref result);
+            wcheb(x, -1.395167e+00, ref tj, ref tj1, ref result);
+            wcheb(x, -2.382823e-01, ref tj, ref tj1, ref result);
+            wcheb(x, -6.531987e-02, ref tj, ref tj1, ref result);
+            wcheb(x, -2.060112e-02, ref tj, ref tj1, ref result);
+            wcheb(x, -8.203697e-03, ref tj, ref tj1, ref result);
+            wcheb(x, -1.516523e-03, ref tj, ref tj1, ref result);
+            wcheb(x, -1.431364e-03, ref tj, ref tj1, ref result);
+            wcheb(x, 6.384553e-04, ref tj, ref tj1, ref result);
+            wcheb(x, -3.238369e-04, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 27)
+        *************************************************************************/
+        private static double w27(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/4.000000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            wcheb(x, -5.089731e+00, ref tj, ref tj1, ref result);
+            wcheb(x, -5.584248e+00, ref tj, ref tj1, ref result);
+            wcheb(x, -1.359966e+00, ref tj, ref tj1, ref result);
+            wcheb(x, -2.203696e-01, ref tj, ref tj1, ref result);
+            wcheb(x, -5.753344e-02, ref tj, ref tj1, ref result);
+            wcheb(x, -1.761891e-02, ref tj, ref tj1, ref result);
+            wcheb(x, -7.096897e-03, ref tj, ref tj1, ref result);
+            wcheb(x, -1.419108e-03, ref tj, ref tj1, ref result);
+            wcheb(x, -1.581214e-03, ref tj, ref tj1, ref result);
+            wcheb(x, 3.033766e-04, ref tj, ref tj1, ref result);
+            wcheb(x, -5.901441e-04, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 28)
+        *************************************************************************/
+        private static double w28(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/4.000000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            wcheb(x, -5.065046e+00, ref tj, ref tj1, ref result);
+            wcheb(x, -5.539163e+00, ref tj, ref tj1, ref result);
+            wcheb(x, -1.328939e+00, ref tj, ref tj1, ref result);
+            wcheb(x, -2.046376e-01, ref tj, ref tj1, ref result);
+            wcheb(x, -5.061515e-02, ref tj, ref tj1, ref result);
+            wcheb(x, -1.469271e-02, ref tj, ref tj1, ref result);
+            wcheb(x, -5.711578e-03, ref tj, ref tj1, ref result);
+            wcheb(x, -8.389153e-04, ref tj, ref tj1, ref result);
+            wcheb(x, -1.250575e-03, ref tj, ref tj1, ref result);
+            wcheb(x, 4.047245e-04, ref tj, ref tj1, ref result);
+            wcheb(x, -5.128555e-04, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 29)
+        *************************************************************************/
+        private static double w29(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/4.000000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            wcheb(x, -5.043413e+00, ref tj, ref tj1, ref result);
+            wcheb(x, -5.499756e+00, ref tj, ref tj1, ref result);
+            wcheb(x, -1.302137e+00, ref tj, ref tj1, ref result);
+            wcheb(x, -1.915129e-01, ref tj, ref tj1, ref result);
+            wcheb(x, -4.516329e-02, ref tj, ref tj1, ref result);
+            wcheb(x, -1.260064e-02, ref tj, ref tj1, ref result);
+            wcheb(x, -4.817269e-03, ref tj, ref tj1, ref result);
+            wcheb(x, -5.478130e-04, ref tj, ref tj1, ref result);
+            wcheb(x, -1.111668e-03, ref tj, ref tj1, ref result);
+            wcheb(x, 4.093451e-04, ref tj, ref tj1, ref result);
+            wcheb(x, -5.135860e-04, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 30)
+        *************************************************************************/
+        private static double w30(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/4.000000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            wcheb(x, -5.024071e+00, ref tj, ref tj1, ref result);
+            wcheb(x, -5.464515e+00, ref tj, ref tj1, ref result);
+            wcheb(x, -1.278342e+00, ref tj, ref tj1, ref result);
+            wcheb(x, -1.800030e-01, ref tj, ref tj1, ref result);
+            wcheb(x, -4.046294e-02, ref tj, ref tj1, ref result);
+            wcheb(x, -1.076162e-02, ref tj, ref tj1, ref result);
+            wcheb(x, -3.968677e-03, ref tj, ref tj1, ref result);
+            wcheb(x, -1.911679e-04, ref tj, ref tj1, ref result);
+            wcheb(x, -8.619185e-04, ref tj, ref tj1, ref result);
+            wcheb(x, 5.125362e-04, ref tj, ref tj1, ref result);
+            wcheb(x, -3.984370e-04, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 40)
+        *************************************************************************/
+        private static double w40(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/4.000000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            wcheb(x, -4.904809e+00, ref tj, ref tj1, ref result);
+            wcheb(x, -5.248327e+00, ref tj, ref tj1, ref result);
+            wcheb(x, -1.136698e+00, ref tj, ref tj1, ref result);
+            wcheb(x, -1.170982e-01, ref tj, ref tj1, ref result);
+            wcheb(x, -1.824427e-02, ref tj, ref tj1, ref result);
+            wcheb(x, -3.888648e-03, ref tj, ref tj1, ref result);
+            wcheb(x, -1.344929e-03, ref tj, ref tj1, ref result);
+            wcheb(x, 2.790407e-04, ref tj, ref tj1, ref result);
+            wcheb(x, -4.619858e-04, ref tj, ref tj1, ref result);
+            wcheb(x, 3.359121e-04, ref tj, ref tj1, ref result);
+            wcheb(x, -2.883026e-04, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 60)
+        *************************************************************************/
+        private static double w60(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/4.000000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            wcheb(x, -4.809656e+00, ref tj, ref tj1, ref result);
+            wcheb(x, -5.077191e+00, ref tj, ref tj1, ref result);
+            wcheb(x, -1.029402e+00, ref tj, ref tj1, ref result);
+            wcheb(x, -7.507931e-02, ref tj, ref tj1, ref result);
+            wcheb(x, -6.506226e-03, ref tj, ref tj1, ref result);
+            wcheb(x, -1.391278e-03, ref tj, ref tj1, ref result);
+            wcheb(x, -4.263635e-04, ref tj, ref tj1, ref result);
+            wcheb(x, 2.302271e-04, ref tj, ref tj1, ref result);
+            wcheb(x, -2.384348e-04, ref tj, ref tj1, ref result);
+            wcheb(x, 1.865587e-04, ref tj, ref tj1, ref result);
+            wcheb(x, -1.622355e-04, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 120)
+        *************************************************************************/
+        private static double w120(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/4.000000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            wcheb(x, -4.729426e+00, ref tj, ref tj1, ref result);
+            wcheb(x, -4.934426e+00, ref tj, ref tj1, ref result);
+            wcheb(x, -9.433231e-01, ref tj, ref tj1, ref result);
+            wcheb(x, -4.492504e-02, ref tj, ref tj1, ref result);
+            wcheb(x, 1.673948e-05, ref tj, ref tj1, ref result);
+            wcheb(x, -6.077014e-04, ref tj, ref tj1, ref result);
+            wcheb(x, -7.215768e-05, ref tj, ref tj1, ref result);
+            wcheb(x, 9.086734e-05, ref tj, ref tj1, ref result);
+            wcheb(x, -8.447980e-05, ref tj, ref tj1, ref result);
+            wcheb(x, 6.705028e-05, ref tj, ref tj1, ref result);
+            wcheb(x, -5.828507e-05, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 200)
+        *************************************************************************/
+        private static double w200(double s)
+        {
+            double result = 0;
+            double x = 0;
+            double tj = 0;
+            double tj1 = 0;
+
+            result = 0;
+            x = Math.Min(2*(s-0.000000e+00)/4.000000e+00-1, 1.0);
+            tj = 1;
+            tj1 = x;
+            wcheb(x, -4.700240e+00, ref tj, ref tj1, ref result);
+            wcheb(x, -4.883080e+00, ref tj, ref tj1, ref result);
+            wcheb(x, -9.132168e-01, ref tj, ref tj1, ref result);
+            wcheb(x, -3.512684e-02, ref tj, ref tj1, ref result);
+            wcheb(x, 1.726342e-03, ref tj, ref tj1, ref result);
+            wcheb(x, -5.189796e-04, ref tj, ref tj1, ref result);
+            wcheb(x, -1.628659e-06, ref tj, ref tj1, ref result);
+            wcheb(x, 4.261786e-05, ref tj, ref tj1, ref result);
+            wcheb(x, -4.002498e-05, ref tj, ref tj1, ref result);
+            wcheb(x, 3.146287e-05, ref tj, ref tj1, ref result);
+            wcheb(x, -2.727576e-05, ref tj, ref tj1, ref result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S,N), S>=0
+        *************************************************************************/
+        private static double wsigma(double s,
+            int n)
+        {
+            double result = 0;
+            double f0 = 0;
+            double f1 = 0;
+            double f2 = 0;
+            double f3 = 0;
+            double f4 = 0;
+            double x0 = 0;
+            double x1 = 0;
+            double x2 = 0;
+            double x3 = 0;
+            double x4 = 0;
+            double x = 0;
+
+            result = 0;
+            if( n==5 )
+            {
+                result = w5(s);
+            }
+            if( n==6 )
+            {
+                result = w6(s);
+            }
+            if( n==7 )
+            {
+                result = w7(s);
+            }
+            if( n==8 )
+            {
+                result = w8(s);
+            }
+            if( n==9 )
+            {
+                result = w9(s);
+            }
+            if( n==10 )
+            {
+                result = w10(s);
+            }
+            if( n==11 )
+            {
+                result = w11(s);
+            }
+            if( n==12 )
+            {
+                result = w12(s);
+            }
+            if( n==13 )
+            {
+                result = w13(s);
+            }
+            if( n==14 )
+            {
+                result = w14(s);
+            }
+            if( n==15 )
+            {
+                result = w15(s);
+            }
+            if( n==16 )
+            {
+                result = w16(s);
+            }
+            if( n==17 )
+            {
+                result = w17(s);
+            }
+            if( n==18 )
+            {
+                result = w18(s);
+            }
+            if( n==19 )
+            {
+                result = w19(s);
+            }
+            if( n==20 )
+            {
+                result = w20(s);
+            }
+            if( n==21 )
+            {
+                result = w21(s);
+            }
+            if( n==22 )
+            {
+                result = w22(s);
+            }
+            if( n==23 )
+            {
+                result = w23(s);
+            }
+            if( n==24 )
+            {
+                result = w24(s);
+            }
+            if( n==25 )
+            {
+                result = w25(s);
+            }
+            if( n==26 )
+            {
+                result = w26(s);
+            }
+            if( n==27 )
+            {
+                result = w27(s);
+            }
+            if( n==28 )
+            {
+                result = w28(s);
+            }
+            if( n==29 )
+            {
+                result = w29(s);
+            }
+            if( n==30 )
+            {
+                result = w30(s);
+            }
+            if( n>30 )
+            {
+                x = 1.0/n;
+                x0 = 1.0/30;
+                f0 = w30(s);
+                x1 = 1.0/40;
+                f1 = w40(s);
+                x2 = 1.0/60;
+                f2 = w60(s);
+                x3 = 1.0/120;
+                f3 = w120(s);
+                x4 = 1.0/200;
+                f4 = w200(s);
+                f1 = ((x-x0)*f1-(x-x1)*f0)/(x1-x0);
+                f2 = ((x-x0)*f2-(x-x2)*f0)/(x2-x0);
+                f3 = ((x-x0)*f3-(x-x3)*f0)/(x3-x0);
+                f4 = ((x-x0)*f4-(x-x4)*f0)/(x4-x0);
+                f2 = ((x-x1)*f2-(x-x2)*f1)/(x2-x1);
+                f3 = ((x-x1)*f3-(x-x3)*f1)/(x3-x1);
+                f4 = ((x-x1)*f4-(x-x4)*f1)/(x4-x1);
+                f3 = ((x-x2)*f3-(x-x3)*f2)/(x3-x2);
+                f4 = ((x-x2)*f4-(x-x4)*f2)/(x4-x2);
+                f4 = ((x-x3)*f4-(x-x4)*f3)/(x4-x3);
+                result = f4;
+            }
+            return result;
+        }
+
+
+    }
+}
+
Index: /trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.5.0/HeuristicLab.ALGLIB-3.5.0/ALGLIB License-3.5.0.txt
===================================================================
--- /trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.5.0/HeuristicLab.ALGLIB-3.5.0/ALGLIB License-3.5.0.txt	(revision 7673)
+++ /trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.5.0/HeuristicLab.ALGLIB-3.5.0/ALGLIB License-3.5.0.txt	(revision 7673)
@@ -0,0 +1,17 @@
+﻿ALGLIB® - numerical analysis library, 1999-2012.
+ALGLIB is registered trademark of the ALGLIB Project.
+
+
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation (www.fsf.org); either version 2 of the 
+License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+A copy of the GNU General Public License is available at
+http://www.fsf.org/licensing/licenses
Index: /trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.5.0/HeuristicLab.ALGLIB-3.5.0/HeuristicLab.ALGLIB-3.5.0.csproj
===================================================================
--- /trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.5.0/HeuristicLab.ALGLIB-3.5.0/HeuristicLab.ALGLIB-3.5.0.csproj	(revision 7673)
+++ /trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.5.0/HeuristicLab.ALGLIB-3.5.0/HeuristicLab.ALGLIB-3.5.0.csproj	(revision 7673)
@@ -0,0 +1,167 @@
+﻿<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProductVersion>9.0.30729</ProductVersion>
+    <SchemaVersion>2.0</SchemaVersion>
+    <ProjectGuid>{65A15C73-FEC3-4E59-AF2E-6539F323068E}</ProjectGuid>
+    <OutputType>Library</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>HeuristicLab.ALGLIB</RootNamespace>
+    <AssemblyName>HeuristicLab.ALGLIB-3.5.0</AssemblyName>
+    <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
+    <TargetFrameworkProfile>
+    </TargetFrameworkProfile>
+    <FileAlignment>512</FileAlignment>
+    <SignAssembly>true</SignAssembly>
+    <AssemblyOriginatorKeyFile>HeuristicLab.snk</AssemblyOriginatorKeyFile>
+    <FileUpgradeFlags>
+    </FileUpgradeFlags>
+    <OldToolsVersion>3.5</OldToolsVersion>
+    <UpgradeBackupLocation />
+    <PublishUrl>publish\</PublishUrl>
+    <Install>true</Install>
+    <InstallFrom>Disk</InstallFrom>
+    <UpdateEnabled>false</UpdateEnabled>
+    <UpdateMode>Foreground</UpdateMode>
+    <UpdateInterval>7</UpdateInterval>
+    <UpdateIntervalUnits>Days</UpdateIntervalUnits>
+    <UpdatePeriodically>false</UpdatePeriodically>
+    <UpdateRequired>false</UpdateRequired>
+    <MapFileExtensions>true</MapFileExtensions>
+    <ApplicationRevision>0</ApplicationRevision>
+    <ApplicationVersion>1.0.0.%2a</ApplicationVersion>
+    <IsWebBootstrapper>false</IsWebBootstrapper>
+    <UseApplicationTrust>false</UseApplicationTrust>
+    <BootstrapperEnabled>true</BootstrapperEnabled>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>$(SolutionDir)\bin\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>$(SolutionDir)\bin\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
+    <DebugSymbols>true</DebugSymbols>
+    <OutputPath>$(SolutionDir)\bin\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <DebugType>full</DebugType>
+    <PlatformTarget>x86</PlatformTarget>
+    <ErrorReport>prompt</ErrorReport>
+    <CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
+    <OutputPath>$(SolutionDir)\bin\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <Optimize>true</Optimize>
+    <DebugType>pdbonly</DebugType>
+    <PlatformTarget>x86</PlatformTarget>
+    <ErrorReport>prompt</ErrorReport>
+    <CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x64' ">
+    <DebugSymbols>true</DebugSymbols>
+    <OutputPath>$(SolutionDir)\bin\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <DebugType>full</DebugType>
+    <PlatformTarget>x64</PlatformTarget>
+    <ErrorReport>prompt</ErrorReport>
+    <CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x64' ">
+    <OutputPath>$(SolutionDir)\bin\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <Optimize>true</Optimize>
+    <DebugType>pdbonly</DebugType>
+    <PlatformTarget>x64</PlatformTarget>
+    <ErrorReport>prompt</ErrorReport>
+    <CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="System" />
+    <Reference Include="System.Core">
+      <RequiredTargetFramework>3.5</RequiredTargetFramework>
+    </Reference>
+    <Reference Include="System.Xml.Linq">
+      <RequiredTargetFramework>3.5</RequiredTargetFramework>
+    </Reference>
+    <Reference Include="System.Data.DataSetExtensions">
+      <RequiredTargetFramework>3.5</RequiredTargetFramework>
+    </Reference>
+    <Reference Include="System.Data" />
+    <Reference Include="System.Xml" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="Plugin.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\..\..\..\HeuristicLab.PluginInfrastructure\3.3\HeuristicLab.PluginInfrastructure-3.3.csproj">
+      <Project>{94186A6A-5176-4402-AE83-886557B53CCA}</Project>
+      <Name>HeuristicLab.PluginInfrastructure-3.3</Name>
+      <Private>False</Private>
+    </ProjectReference>
+    <ProjectReference Include="..\ALGLIB-3.5.0\ALGLIB-3.5.0.csproj">
+      <Project>{D863807E-4774-455E-85A4-96E7E6A8F68E}</Project>
+      <Name>ALGLIB-3.5.0</Name>
+    </ProjectReference>
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="HeuristicLab.snk" />
+    <None Include="Plugin.cs.frame" />
+    <None Include="Properties\AssemblyInfo.cs.frame" />
+  </ItemGroup>
+  <ItemGroup>
+    <BootstrapperPackage Include="Microsoft.Net.Client.3.5">
+      <Visible>False</Visible>
+      <ProductName>.NET Framework 3.5 SP1 Client Profile</ProductName>
+      <Install>false</Install>
+    </BootstrapperPackage>
+    <BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
+      <Visible>False</Visible>
+      <ProductName>.NET Framework 3.5 SP1</ProductName>
+      <Install>true</Install>
+    </BootstrapperPackage>
+    <BootstrapperPackage Include="Microsoft.Windows.Installer.3.1">
+      <Visible>False</Visible>
+      <ProductName>Windows Installer 3.1</ProductName>
+      <Install>true</Install>
+    </BootstrapperPackage>
+  </ItemGroup>
+  <ItemGroup>
+    <Content Include="ALGLIB License-3.5.0.txt">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
+       Other similar extension points exist, see Microsoft.Common.targets.
+  <Target Name="BeforeBuild">
+  </Target>
+  <Target Name="AfterBuild">
+  </Target>
+  -->
+  <PropertyGroup>
+    <PreBuildEvent>set Path=%25Path%25;$(ProjectDir);$(SolutionDir)
+set ProjectDir=$(ProjectDir)
+set SolutionDir=$(SolutionDir)
+set Outdir=$(Outdir)
+
+call PreBuildEvent.cmd
+</PreBuildEvent>
+  </PropertyGroup>
+</Project>
Index: /trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.5.0/HeuristicLab.ALGLIB-3.5.0/HeuristicLab.ALGLIB-3.5.0.csproj.user
===================================================================
--- /trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.5.0/HeuristicLab.ALGLIB-3.5.0/HeuristicLab.ALGLIB-3.5.0.csproj.user	(revision 7673)
+++ /trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.5.0/HeuristicLab.ALGLIB-3.5.0/HeuristicLab.ALGLIB-3.5.0.csproj.user	(revision 7673)
@@ -0,0 +1,6 @@
+﻿<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <ProjectView>ShowAllFiles</ProjectView>
+  </PropertyGroup>
+</Project>
Index: /trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.5.0/HeuristicLab.ALGLIB-3.5.0/Plugin.cs.frame
===================================================================
--- /trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.5.0/HeuristicLab.ALGLIB-3.5.0/Plugin.cs.frame	(revision 7673)
+++ /trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.5.0/HeuristicLab.ALGLIB-3.5.0/Plugin.cs.frame	(revision 7673)
@@ -0,0 +1,35 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2012 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
+ *
+ * This file is part of HeuristicLab.
+ *
+ * HeuristicLab is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * HeuristicLab is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with HeuristicLab. If not, see <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using HeuristicLab.PluginInfrastructure;
+
+namespace HeuristicLab.ALGLIB {
+  [Plugin("HeuristicLab.ALGLIB","HeuristicLab wrapper for ALGLIB® - numerical analysis library","3.5.0.$WCREV$")]  
+  [PluginFile("HeuristicLab.ALGLIB-3.5.0.dll", PluginFileType.Assembly)]
+  [PluginFile("ALGLIB-3.5.0.dll", PluginFileType.Assembly)]
+  [PluginFile("ALGLIB License-3.5.0.txt", PluginFileType.License)]
+  public class HeuristicLabAlgLibPlugin : PluginBase {
+  }
+}
Index: /trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.5.0/HeuristicLab.ALGLIB-3.5.0/Properties/AssemblyInfo.cs.frame
===================================================================
--- /trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.5.0/HeuristicLab.ALGLIB-3.5.0/Properties/AssemblyInfo.cs.frame	(revision 7673)
+++ /trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.5.0/HeuristicLab.ALGLIB-3.5.0/Properties/AssemblyInfo.cs.frame	(revision 7673)
@@ -0,0 +1,37 @@
+﻿using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using HeuristicLab.PluginInfrastructure;
+
+// General Information about an assembly is controlled through the following 
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("HeuristicLab.ALGLIB")]
+[assembly: AssemblyDescription("Provides an interface to ALGLIB")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("HeuristicLab")]
+[assembly: AssemblyCopyright("(c) 2002-2012 HEAL")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible 
+// to COM components.  If you need to access a type in this assembly from 
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("58AFADCE-0576-4BFF-914B-D6339CD7CFE1")]
+
+// Version information for an assembly consists of the following four values:
+//
+//      Major Version
+//      Minor Version 
+//      Build Number
+//      Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers 
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("3.5.0")]
+[assembly: AssemblyFileVersion("3.5.0.$WCREV$")]
